From 1087ec431e57d3d82ccff4d914dea9aeeb54503d Mon Sep 17 00:00:00 2001 From: dfalty Date: Wed, 6 Dec 2023 10:53:51 -0600 Subject: [PATCH 01/71] Add remaining unit/integration tests --- .github/workflows/positron-ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/positron-ci.yml b/.github/workflows/positron-ci.yml index 7aa4ef2b4ef..f35d8179ee1 100644 --- a/.github/workflows/positron-ci.yml +++ b/.github/workflows/positron-ci.yml @@ -55,6 +55,10 @@ jobs: - name: Compile Integration Tests run: yarn --cwd test/integration/browser compile + - name: Run Unit Tests (Electron) + id: electron-unit-tests + run: DISPLAY=:10 ./scripts/test.sh + - name: Run Unit Tests (node.js) id: nodejs-unit-tests run: yarn test-node @@ -66,3 +70,12 @@ jobs: - name: Run Integration Tests (Electron) id: electron-integration-tests run: DISPLAY=:10 ./scripts/test-integration.sh + + - name: Run Integration Tests (Browser, Chromium) + id: browser-integration-tests + run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser chromium + + - name: Run Integration Tests (Remote) + id: electron-remote-integration-tests + timeout-minutes: 15 + run: DISPLAY=:10 ./scripts/test-remote-integration.sh From 44454a9a07077818bd30cd77e98f591d0dc2cc2a Mon Sep 17 00:00:00 2001 From: dfalty Date: Wed, 6 Dec 2023 13:33:12 -0600 Subject: [PATCH 02/71] Removing Browser Integration Tests to debug --- .github/workflows/positron-ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/positron-ci.yml b/.github/workflows/positron-ci.yml index f35d8179ee1..2ac70f682e6 100644 --- a/.github/workflows/positron-ci.yml +++ b/.github/workflows/positron-ci.yml @@ -71,10 +71,6 @@ jobs: id: electron-integration-tests run: DISPLAY=:10 ./scripts/test-integration.sh - - name: Run Integration Tests (Browser, Chromium) - id: browser-integration-tests - run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser chromium - - name: Run Integration Tests (Remote) id: electron-remote-integration-tests timeout-minutes: 15 From b1da453a1242347959c25d10e617991cbc22b82f Mon Sep 17 00:00:00 2001 From: dfalty Date: Wed, 6 Dec 2023 15:54:50 -0600 Subject: [PATCH 03/71] Adding positron-code-cells to test-remote --- scripts/test-remote-integration.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/test-remote-integration.sh b/scripts/test-remote-integration.sh index d3e9f2a5265..07d2753172b 100755 --- a/scripts/test-remote-integration.sh +++ b/scripts/test-remote-integration.sh @@ -124,6 +124,14 @@ echo "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS --folder-uri=$AUTHORITY$(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$REMOTE_VSCODE/configuration-editing --extensionTestsPath=$REMOTE_VSCODE/configuration-editing/out/test $API_TESTS_EXTRA_ARGS $EXTRA_INTEGRATION_TEST_ARGUMENTS kill_app +# Positron Extensions + +echo +echo "### Positron Code Cells tests" +echo +yarn test-extension -l positron-code-cells +kill_app + # Cleanup if [[ "$3" == "" ]]; then From 84f5b6f8db924a22ea9686bf2b334ae234ea4679 Mon Sep 17 00:00:00 2001 From: dfalty Date: Fri, 8 Dec 2023 14:45:03 -0600 Subject: [PATCH 04/71] Adding Smoke Tests with example Variables test --- .github/workflows/positron-ci.yml | 4 ++ .../positron/variables/variablespane.test.ts | 20 +++++++ test/smoke/src/main.ts | 52 ++++++++++--------- 3 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 test/smoke/src/areas/positron/variables/variablespane.test.ts diff --git a/.github/workflows/positron-ci.yml b/.github/workflows/positron-ci.yml index 2ac70f682e6..c862b79ed75 100644 --- a/.github/workflows/positron-ci.yml +++ b/.github/workflows/positron-ci.yml @@ -75,3 +75,7 @@ jobs: id: electron-remote-integration-tests timeout-minutes: 15 run: DISPLAY=:10 ./scripts/test-remote-integration.sh + + - name: Run Smoke Tests (Electron) + id: electron-smoke-tests + run: DISPLAY=:10 yarn smoketest-no-compile --tracing diff --git a/test/smoke/src/areas/positron/variables/variablespane.test.ts b/test/smoke/src/areas/positron/variables/variablespane.test.ts new file mode 100644 index 00000000000..5194d8480bb --- /dev/null +++ b/test/smoke/src/areas/positron/variables/variablespane.test.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { Application, Logger } from '../../../../../automation'; +import { installAllHandlers } from '../../../utils'; + +export function setup(logger: Logger) { + describe('Variables Pane', () => { + + // Shared before/after handling + installAllHandlers(logger); + + it('verifies Variables pane exists', async function () { + const app = this.app as Application; + + await app.code.waitForElement('.positron-variables-container'); + }); + }); +} diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index ef9e04f39bf..de8ec782eed 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -16,18 +16,20 @@ import fetch from 'node-fetch'; import { Quality, MultiLogger, Logger, ConsoleLogger, FileLogger, measureAndLog, getDevElectronPath, getBuildElectronPath, getBuildVersion } from '../../automation'; import { retry, timeout } from './utils'; -import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; -import { setup as setupPreferencesTests } from './areas/preferences/preferences.test'; -import { setup as setupSearchTests } from './areas/search/search.test'; -import { setup as setupNotebookTests } from './areas/notebook/notebook.test'; -import { setup as setupLanguagesTests } from './areas/languages/languages.test'; -import { setup as setupStatusbarTests } from './areas/statusbar/statusbar.test'; -import { setup as setupExtensionTests } from './areas/extensions/extensions.test'; -import { setup as setupMultirootTests } from './areas/multiroot/multiroot.test'; -import { setup as setupLocalizationTests } from './areas/workbench/localization.test'; -import { setup as setupLaunchTests } from './areas/workbench/launch.test'; -import { setup as setupTerminalTests } from './areas/terminal/terminal.test'; -import { setup as setupTaskTests } from './areas/task/task.test'; +// Disabling all but Positron Tests for now +// import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; +// import { setup as setupPreferencesTests } from './areas/preferences/preferences.test'; +// import { setup as setupSearchTests } from './areas/search/search.test'; +// import { setup as setupNotebookTests } from './areas/notebook/notebook.test'; +// import { setup as setupLanguagesTests } from './areas/languages/languages.test'; +// import { setup as setupStatusbarTests } from './areas/statusbar/statusbar.test'; +// import { setup as setupExtensionTests } from './areas/extensions/extensions.test'; +// import { setup as setupMultirootTests } from './areas/multiroot/multiroot.test'; +// import { setup as setupLocalizationTests } from './areas/workbench/localization.test'; +// import { setup as setupLaunchTests } from './areas/workbench/launch.test'; +// import { setup as setupTerminalTests } from './areas/terminal/terminal.test'; +// import { setup as setupTaskTests } from './areas/task/task.test'; +import { setup as setupVariablesTest } from './areas/positron/variables/variablespane.test'; const rootPath = path.join(__dirname, '..', '..', '..'); @@ -397,16 +399,18 @@ after(async function () { }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { - if (!opts.web) { setupDataLossTests(() => opts['stable-build'] /* Do not change, deferred for a reason! */, logger); } - setupPreferencesTests(logger); - setupSearchTests(logger); - setupNotebookTests(logger); - setupLanguagesTests(logger); - if (opts.web) { setupTerminalTests(logger); } // Not stable on desktop/remote https://github.com/microsoft/vscode/issues/146811 - setupTaskTests(logger); - setupStatusbarTests(logger); - if (quality !== Quality.Dev && quality !== Quality.OSS) { setupExtensionTests(logger); } - setupMultirootTests(logger); - if (!opts.web && !opts.remote && quality !== Quality.Dev && quality !== Quality.OSS) { setupLocalizationTests(logger); } - if (!opts.web && !opts.remote) { setupLaunchTests(logger); } + // Disabling all but Positron Tests for now + // if (!opts.web) { setupDataLossTests(() => opts['stable-build'] /* Do not change, deferred for a reason! */, logger); } + // setupPreferencesTests(logger); + // setupSearchTests(logger); + // setupNotebookTests(logger); + // setupLanguagesTests(logger); + // if (opts.web) { setupTerminalTests(logger); } // Not stable on desktop/remote https://github.com/microsoft/vscode/issues/146811 + // setupTaskTests(logger); + // setupStatusbarTests(logger); + // if (quality !== Quality.Dev && quality !== Quality.OSS) { setupExtensionTests(logger); } + // setupMultirootTests(logger); + // if (!opts.web && !opts.remote && quality !== Quality.Dev && quality !== Quality.OSS) { setupLocalizationTests(logger); } + // if (!opts.web && !opts.remote) { setupLaunchTests(logger); } + setupVariablesTest(logger); }); From 416e45bfb993ed733672bf42b70fa2fc16b44211 Mon Sep 17 00:00:00 2001 From: dfalty Date: Mon, 11 Dec 2023 07:59:53 -0600 Subject: [PATCH 05/71] Isolating Electron Integration tests --- .github/workflows/positron-ci.yml | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/positron-ci.yml b/.github/workflows/positron-ci.yml index c862b79ed75..2fa28463e57 100644 --- a/.github/workflows/positron-ci.yml +++ b/.github/workflows/positron-ci.yml @@ -55,27 +55,27 @@ jobs: - name: Compile Integration Tests run: yarn --cwd test/integration/browser compile - - name: Run Unit Tests (Electron) - id: electron-unit-tests - run: DISPLAY=:10 ./scripts/test.sh + # - name: Run Unit Tests (Electron) + # id: electron-unit-tests + # run: DISPLAY=:10 ./scripts/test.sh - - name: Run Unit Tests (node.js) - id: nodejs-unit-tests - run: yarn test-node + # - name: Run Unit Tests (node.js) + # id: nodejs-unit-tests + # run: yarn test-node - - name: Run Unit Tests (Browser, Chromium) - id: browser-unit-tests - run: DISPLAY=:10 yarn test-browser-no-install --browser chromium + # - name: Run Unit Tests (Browser, Chromium) + # id: browser-unit-tests + # run: DISPLAY=:10 yarn test-browser-no-install --browser chromium - name: Run Integration Tests (Electron) id: electron-integration-tests run: DISPLAY=:10 ./scripts/test-integration.sh - - name: Run Integration Tests (Remote) - id: electron-remote-integration-tests - timeout-minutes: 15 - run: DISPLAY=:10 ./scripts/test-remote-integration.sh + # - name: Run Integration Tests (Remote) + # id: electron-remote-integration-tests + # timeout-minutes: 15 + # run: DISPLAY=:10 ./scripts/test-remote-integration.sh - - name: Run Smoke Tests (Electron) - id: electron-smoke-tests - run: DISPLAY=:10 yarn smoketest-no-compile --tracing + # - name: Run Smoke Tests (Electron) + # id: electron-smoke-tests + # run: DISPLAY=:10 yarn smoketest-no-compile --tracing From 59ceed9d890166f2c54bda2291eae33cf844568f Mon Sep 17 00:00:00 2001 From: dfalty Date: Mon, 11 Dec 2023 10:53:59 -0600 Subject: [PATCH 06/71] Adding back full suite of tests --- .github/workflows/positron-ci.yml | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/positron-ci.yml b/.github/workflows/positron-ci.yml index 2fa28463e57..f738e7720f1 100644 --- a/.github/workflows/positron-ci.yml +++ b/.github/workflows/positron-ci.yml @@ -13,7 +13,7 @@ jobs: linux: name: Tests on Linux runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 45 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} POSITRON_BUILD_NUMBER: 0 # CI skips building releases @@ -55,27 +55,27 @@ jobs: - name: Compile Integration Tests run: yarn --cwd test/integration/browser compile - # - name: Run Unit Tests (Electron) - # id: electron-unit-tests - # run: DISPLAY=:10 ./scripts/test.sh + - name: Run Unit Tests (Electron) + id: electron-unit-tests + run: DISPLAY=:10 ./scripts/test.sh - # - name: Run Unit Tests (node.js) - # id: nodejs-unit-tests - # run: yarn test-node + - name: Run Unit Tests (node.js) + id: nodejs-unit-tests + run: yarn test-node - # - name: Run Unit Tests (Browser, Chromium) - # id: browser-unit-tests - # run: DISPLAY=:10 yarn test-browser-no-install --browser chromium + - name: Run Unit Tests (Browser, Chromium) + id: browser-unit-tests + run: DISPLAY=:10 yarn test-browser-no-install --browser chromium - name: Run Integration Tests (Electron) id: electron-integration-tests run: DISPLAY=:10 ./scripts/test-integration.sh - # - name: Run Integration Tests (Remote) - # id: electron-remote-integration-tests - # timeout-minutes: 15 - # run: DISPLAY=:10 ./scripts/test-remote-integration.sh + - name: Run Integration Tests (Remote) + id: electron-remote-integration-tests + timeout-minutes: 15 + run: DISPLAY=:10 ./scripts/test-remote-integration.sh - # - name: Run Smoke Tests (Electron) - # id: electron-smoke-tests - # run: DISPLAY=:10 yarn smoketest-no-compile --tracing + - name: Run Smoke Tests (Electron) + id: electron-smoke-tests + run: DISPLAY=:10 yarn smoketest-no-compile --tracing From e1e33d8e3fd5b52a04ae240f30a2e11883ab0259 Mon Sep 17 00:00:00 2001 From: dfalty Date: Mon, 11 Dec 2023 11:39:11 -0600 Subject: [PATCH 07/71] Turning off all but Smoke tests --- .github/workflows/positron-ci.yml | 43 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/.github/workflows/positron-ci.yml b/.github/workflows/positron-ci.yml index f738e7720f1..d8b4c7808b6 100644 --- a/.github/workflows/positron-ci.yml +++ b/.github/workflows/positron-ci.yml @@ -55,26 +55,29 @@ jobs: - name: Compile Integration Tests run: yarn --cwd test/integration/browser compile - - name: Run Unit Tests (Electron) - id: electron-unit-tests - run: DISPLAY=:10 ./scripts/test.sh - - - name: Run Unit Tests (node.js) - id: nodejs-unit-tests - run: yarn test-node - - - name: Run Unit Tests (Browser, Chromium) - id: browser-unit-tests - run: DISPLAY=:10 yarn test-browser-no-install --browser chromium - - - name: Run Integration Tests (Electron) - id: electron-integration-tests - run: DISPLAY=:10 ./scripts/test-integration.sh - - - name: Run Integration Tests (Remote) - id: electron-remote-integration-tests - timeout-minutes: 15 - run: DISPLAY=:10 ./scripts/test-remote-integration.sh + - name: Compile Smoke Tests + run: yarn --cwd test/smoke compile + + # - name: Run Unit Tests (Electron) + # id: electron-unit-tests + # run: DISPLAY=:10 ./scripts/test.sh + + # - name: Run Unit Tests (node.js) + # id: nodejs-unit-tests + # run: yarn test-node + + # - name: Run Unit Tests (Browser, Chromium) + # id: browser-unit-tests + # run: DISPLAY=:10 yarn test-browser-no-install --browser chromium + + # - name: Run Integration Tests (Electron) + # id: electron-integration-tests + # run: DISPLAY=:10 ./scripts/test-integration.sh + + # - name: Run Integration Tests (Remote) + # id: electron-remote-integration-tests + # timeout-minutes: 15 + # run: DISPLAY=:10 ./scripts/test-remote-integration.sh - name: Run Smoke Tests (Electron) id: electron-smoke-tests From fcc2843736554f6336beb0594af44014ae5004bf Mon Sep 17 00:00:00 2001 From: dfalty Date: Mon, 11 Dec 2023 11:52:24 -0600 Subject: [PATCH 08/71] Turning back on all tests --- .github/workflows/positron-ci.yml | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/positron-ci.yml b/.github/workflows/positron-ci.yml index d8b4c7808b6..dfdfb8ded33 100644 --- a/.github/workflows/positron-ci.yml +++ b/.github/workflows/positron-ci.yml @@ -58,26 +58,26 @@ jobs: - name: Compile Smoke Tests run: yarn --cwd test/smoke compile - # - name: Run Unit Tests (Electron) - # id: electron-unit-tests - # run: DISPLAY=:10 ./scripts/test.sh - - # - name: Run Unit Tests (node.js) - # id: nodejs-unit-tests - # run: yarn test-node - - # - name: Run Unit Tests (Browser, Chromium) - # id: browser-unit-tests - # run: DISPLAY=:10 yarn test-browser-no-install --browser chromium - - # - name: Run Integration Tests (Electron) - # id: electron-integration-tests - # run: DISPLAY=:10 ./scripts/test-integration.sh - - # - name: Run Integration Tests (Remote) - # id: electron-remote-integration-tests - # timeout-minutes: 15 - # run: DISPLAY=:10 ./scripts/test-remote-integration.sh + - name: Run Unit Tests (Electron) + id: electron-unit-tests + run: DISPLAY=:10 ./scripts/test.sh + + - name: Run Unit Tests (node.js) + id: nodejs-unit-tests + run: yarn test-node + + - name: Run Unit Tests (Browser, Chromium) + id: browser-unit-tests + run: DISPLAY=:10 yarn test-browser-no-install --browser chromium + + - name: Run Integration Tests (Electron) + id: electron-integration-tests + run: DISPLAY=:10 ./scripts/test-integration.sh + + - name: Run Integration Tests (Remote) + id: electron-remote-integration-tests + timeout-minutes: 15 + run: DISPLAY=:10 ./scripts/test-remote-integration.sh - name: Run Smoke Tests (Electron) id: electron-smoke-tests From 9db255ea23265db6e032e4de76d0f5c032d3a92c Mon Sep 17 00:00:00 2001 From: dfalty Date: Tue, 12 Dec 2023 16:42:02 -0600 Subject: [PATCH 09/71] Removing tests from every PR and creating full test suite job --- .github/workflows/positron-ci.yml | 13 ---- .github/workflows/positron-full-test.yml | 82 ++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/positron-full-test.yml diff --git a/.github/workflows/positron-ci.yml b/.github/workflows/positron-ci.yml index dfdfb8ded33..bbdacd657b2 100644 --- a/.github/workflows/positron-ci.yml +++ b/.github/workflows/positron-ci.yml @@ -58,27 +58,14 @@ jobs: - name: Compile Smoke Tests run: yarn --cwd test/smoke compile - - name: Run Unit Tests (Electron) - id: electron-unit-tests - run: DISPLAY=:10 ./scripts/test.sh - - name: Run Unit Tests (node.js) id: nodejs-unit-tests run: yarn test-node - - name: Run Unit Tests (Browser, Chromium) - id: browser-unit-tests - run: DISPLAY=:10 yarn test-browser-no-install --browser chromium - - name: Run Integration Tests (Electron) id: electron-integration-tests run: DISPLAY=:10 ./scripts/test-integration.sh - - name: Run Integration Tests (Remote) - id: electron-remote-integration-tests - timeout-minutes: 15 - run: DISPLAY=:10 ./scripts/test-remote-integration.sh - - name: Run Smoke Tests (Electron) id: electron-smoke-tests run: DISPLAY=:10 yarn smoketest-no-compile --tracing diff --git a/.github/workflows/positron-full-test.yml b/.github/workflows/positron-full-test.yml new file mode 100644 index 00000000000..2e7f3ae0de6 --- /dev/null +++ b/.github/workflows/positron-full-test.yml @@ -0,0 +1,82 @@ +name: "Positron: Full Test Suite" + +# Run tests daily at 4am UTC (11p EST) on weekdays for now, or manually +on: + schedule: + - cron: "0 4 * * 1-5" + workflow_dispatch: + +jobs: + + linux: + name: Tests on Linux + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + POSITRON_BUILD_NUMBER: 0 # CI skips building releases + steps: + - uses: actions/checkout@v4 + + - name: Setup Build Environment + run: | + sudo apt-get update + sudo apt-get install -y vim curl build-essential clang make cmake git r-base-dev python3-pip python-is-python3 libsodium-dev libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 libnss3 libnspr4 libasound2 libkrb5-dev + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + - uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Execute yarn + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + POSITRON_GITHUB_PAT: ${{ secrets.POSITRON_GITHUB_PAT }} + run: | + # Install Yarn + npm install -g yarn + + # Install node-gyp; this is required by some packages, and yarn + # sometimes fails to automatically install it. + yarn global add node-gyp + + # Perform the main yarn command; this installs all Node packages and + # dependencies + yarn --immutable --network-timeout 120000 + + - name: Compile and Download + run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions + + - name: Compile Integration Tests + run: yarn --cwd test/integration/browser compile + + - name: Compile Smoke Tests + run: yarn --cwd test/smoke compile + + - name: Run Unit Tests (Electron) + id: electron-unit-tests + run: DISPLAY=:10 ./scripts/test.sh + + - name: Run Unit Tests (node.js) + id: nodejs-unit-tests + run: yarn test-node + + - name: Run Unit Tests (Browser, Chromium) + id: browser-unit-tests + run: DISPLAY=:10 yarn test-browser-no-install --browser chromium + + - name: Run Integration Tests (Electron) + id: electron-integration-tests + run: DISPLAY=:10 ./scripts/test-integration.sh + + - name: Run Integration Tests (Remote) + id: electron-remote-integration-tests + timeout-minutes: 15 + run: DISPLAY=:10 ./scripts/test-remote-integration.sh + + - name: Run Smoke Tests (Electron) + id: electron-smoke-tests + run: DISPLAY=:10 yarn smoketest-no-compile --tracing From f0c68f37aff3bdf16c6db6506ee4a28b2e7d6724 Mon Sep 17 00:00:00 2001 From: dfalty Date: Thu, 14 Dec 2023 09:20:26 -0600 Subject: [PATCH 10/71] Increasing timeout for full suite --- .github/workflows/positron-full-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/positron-full-test.yml b/.github/workflows/positron-full-test.yml index 2e7f3ae0de6..510b7db3767 100644 --- a/.github/workflows/positron-full-test.yml +++ b/.github/workflows/positron-full-test.yml @@ -11,7 +11,7 @@ jobs: linux: name: Tests on Linux runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 60 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} POSITRON_BUILD_NUMBER: 0 # CI skips building releases From e96eeee7f47a9c76a142359ee7e51ced7fb05381 Mon Sep 17 00:00:00 2001 From: dfalty Date: Thu, 14 Dec 2023 09:36:55 -0600 Subject: [PATCH 11/71] Adding Browser Integration tests --- .github/workflows/positron-full-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/positron-full-test.yml b/.github/workflows/positron-full-test.yml index 510b7db3767..d99383c3e0d 100644 --- a/.github/workflows/positron-full-test.yml +++ b/.github/workflows/positron-full-test.yml @@ -77,6 +77,10 @@ jobs: timeout-minutes: 15 run: DISPLAY=:10 ./scripts/test-remote-integration.sh + - name: Run Integration Tests (Browser, Chromium) + id: browser-integration-tests + run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser chromium + - name: Run Smoke Tests (Electron) id: electron-smoke-tests run: DISPLAY=:10 yarn smoketest-no-compile --tracing From e74cc0e8f57f4a7aec2f10d28302f71d99d6f551 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Thu, 14 Dec 2023 16:38:34 -0800 Subject: [PATCH 12/71] start sketching out variables backend --- positron/comms/variables-backend-openrpc.json | 175 ++++++++++++++++++ positron/comms/variables.json | 9 + 2 files changed, 184 insertions(+) create mode 100644 positron/comms/variables-backend-openrpc.json create mode 100644 positron/comms/variables.json diff --git a/positron/comms/variables-backend-openrpc.json b/positron/comms/variables-backend-openrpc.json new file mode 100644 index 00000000000..117c37f2735 --- /dev/null +++ b/positron/comms/variables-backend-openrpc.json @@ -0,0 +1,175 @@ +{ + "openrpc": "1.3.0", + "info": { + "title": "Variables Backend", + "version": "1.0.0" + }, + "methods": [ + { + "name": "clear", + "summary": "Clear all variables", + "description": "Clears (deletes) all variables in the current session.", + "params": [ + { + "name": "include_hidden_objects", + "description": "Whether to clear hidden objects in addition to normal variables", + "schema": { + "type": "boolean" + } + } + ] + }, + { + "name": "delete", + "summary": "Deletes a set of named variables", + "description": "Deletes the named variables from the current session.", + "params": [ + { + "name": "names", + "description": "The names of the variables to delete.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ] + }, + { + "name": "inspect", + "summary": "Inspect a variable", + "description": "Returns the children of a variable, as an array of variables.", + "params": [ + { + "name": "path", + "description": "The path to the variable to inspect, as an array of access keys.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "result": { + "schema": { + "type": "object", + "properties": { + "children": { + "type": "array", + "description": "The children of the inspected variable.", + "items": { + "$ref": "#/components/schemas/variable" + } + }, + "length": { + "type": "integer", + "description": "The total number of children. This may be greater than the number of children in the 'children' array if the array is truncated." + } + } + } + } + }, + { + "name": "clipboard_format", + "summary": "Format for clipboard", + "description": "Requests a formatted representation of a variable for copying to the clipboard.", + "params": [ + { + "name": "path", + "description": "The path to the variable to format, as an array of access keys.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "format", + "description": "The requested format for the variable, as a MIME type", + "schema": { + "type": "string" + } + } + ], + "result": { + "schema": { + "type": "object", + "properties": { + "format": { + "type": "string", + "description": "The format returned, as a MIME type; matches the MIME type of the format named in the request." + }, + "content": { + "type": "string", + "description": "The formatted content of the variable." + } + } + } + } + } + ], + "components": { + "schemas": { + "variable": { + "type": "object", + "properties": { + "access_key": { + "type": "string", + "description": "A key that uniquely identifies the variable within the runtime and can be used to access the variable in `inspect` requests" + }, + "display_name": { + "type": "string", + "description": "The name of the variable, formatted for display" + }, + "display_value": { + "type": "string", + "description": "A string representation of the variable's value, formatted for display and possibly truncated" + }, + "display_type": { + "type": "string", + "description": "The variable's type, formatted for display" + }, + "type_info": { + "type": "string", + "description": "Extended information about the variable's type" + }, + "kind": { + "type": "string", + "enum": [ + "boolean", + "bytes", + "collection", + "empty", + "function", + "map", + "number", + "other", + "string", + "table" + ], + "description": "The kind of value the variable represents, such as 'string' or 'number'" + }, + "length": { + "type": "integer", + "description": "The number of elements in the variable, if it is a collection" + }, + "has_children": { + "type": "boolean", + "description": "Whether the variable has child variables" + }, + "has_viewer": { + "type": "boolean", + "description": "True if there is a viewer available for this variable (i.e. the runtime can handle a 'view' request for this variable)" + }, + "is_truncated": { + "type": "boolean", + "description": "True the 'value' field is a truncated representation of the variable's value" + } + } + } + } + } +} diff --git a/positron/comms/variables.json b/positron/comms/variables.json new file mode 100644 index 00000000000..680410d9e97 --- /dev/null +++ b/positron/comms/variables.json @@ -0,0 +1,9 @@ +{ + "name": "variables", + "initiator": "frontend", + "initial_data": { + "schema": { + "type": "null" + } + } +} From af79cc6a6d9c549628e7959472ff591ff410ac42 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Fri, 15 Dec 2023 13:36:06 -0800 Subject: [PATCH 13/71] initial generated version of variables comm (ts) --- positron/comms/generate-comms.ts | 31 ++++-- positron/comms/variables-backend-openrpc.json | 10 +- .../common/positronPlotComm.ts | 6 +- .../common/positronVariablesComm.ts | 104 ++++++++++++++++++ 4 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 54d2ffeae87..9f59fcfaa4a 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -256,7 +256,7 @@ use serde::Serialize; if (method.params.length > 0) { yield `(${snakeCaseToSentenceCase(method.name)}Params),\n`; } else { - yield ',\n'; + yield ',\n\n'; } } yield `}\n\n`; @@ -517,15 +517,24 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (method.result && method.result.schema && method.result.schema.type === 'object') { - yield await compile(method.result.schema, - method.result.schema.name, { - bannerComment: '', - additionalProperties: false, - style: { - useTabs: true + yield '/**\n'; + yield formatComment(' * ', method.result.schema.description); + yield ' */\n'; + yield `export interface ${snakeCaseToSentenceCase(method.result.schema.name)} {\n`; + for (const prop of Object.keys(method.result.schema.properties)) { + const schema = method.result.schema.properties[prop]; + yield '\t/**\n'; + yield formatComment('\t * ', schema.description); + yield '\t */\n'; + yield `\t${prop}: `; + if (schema.type === 'object') { + yield snakeCaseToSentenceCase(schema.name); + } else { + yield TypescriptTypeMap[schema.type]; } - }); - yield '\n'; + yield `;\n\n`; + } + yield '}\n\n'; } } } @@ -599,7 +608,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co `@param ${snakeCaseToCamelCase(param.name)} ${param.description}`); } yield `\t *\n`; - if (method.result) { + if (method.result && method.result.schema) { yield formatComment('\t * ', `@returns ${method.result.schema.description}`); } @@ -615,7 +624,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co } } yield '): Promise<'; - if (method.result) { + if (method.result && method.result.schema) { if (method.result.schema.type === 'object') { yield snakeCaseToSentenceCase(method.result.schema.name); } else { diff --git a/positron/comms/variables-backend-openrpc.json b/positron/comms/variables-backend-openrpc.json index 117c37f2735..1e69ed8b5d7 100644 --- a/positron/comms/variables-backend-openrpc.json +++ b/positron/comms/variables-backend-openrpc.json @@ -17,7 +17,8 @@ "type": "boolean" } } - ] + ], + "result": {} }, { "name": "delete", @@ -34,7 +35,8 @@ } } } - ] + ], + "result": {} }, { "name": "inspect", @@ -55,6 +57,8 @@ "result": { "schema": { "type": "object", + "name": "inspected_variable", + "description": "An inspected variable.", "properties": { "children": { "type": "array", @@ -97,6 +101,8 @@ "result": { "schema": { "type": "object", + "name": "formatted_variable", + "description": "An object formatted for copying to the clipboard.", "properties": { "format": { "type": "string", diff --git a/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts b/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts index ca1463eb74b..1bb8e08fd21 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts @@ -17,11 +17,13 @@ export interface PlotResult { /** * The plot data, as a base64-encoded string */ - data?: string; + data: string; + /** * The MIME type of the plot data */ - mime_type?: string; + mime_type: string; + } /** diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts new file mode 100644 index 00000000000..44bd1702815 --- /dev/null +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// +// AUTO-GENERATED from variables.json; do not edit. +// + +import { Event } from 'vs/base/common/event'; +import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; +import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; + +/** + * An inspected variable. + */ +export interface InspectedVariable { + /** + * The children of the inspected variable. + */ + children: Array; + + /** + * The total number of children. This may be greater than the number of + * children in the 'children' array if the array is truncated. + */ + length: number; + +} + +/** + * An object formatted for copying to the clipboard. + */ +export interface FormattedVariable { + /** + * The format returned, as a MIME type; matches the MIME type of the + * format named in the request. + */ + format: string; + + /** + * The formatted content of the variable. + */ + content: string; + +} + +export class PositronVariablesComm extends PositronBaseComm { + constructor(instance: IRuntimeClientInstance) { + super(instance); + } + + /** + * Clear all variables + * + * Clears (deletes) all variables in the current session. + * + * @param includeHiddenObjects Whether to clear hidden objects in + * addition to normal variables + * + */ + clear(includeHiddenObjects: boolean): Promise<> { + return super.performRpc('clear', ['include_hidden_objects'], [includeHiddenObjects]); + } + /** + * Deletes a set of named variables + * + * Deletes the named variables from the current session. + * + * @param names The names of the variables to delete. + * + */ + delete(names: Array): Promise<> { + return super.performRpc('delete', ['names'], [names]); + } + /** + * Inspect a variable + * + * Returns the children of a variable, as an array of variables. + * + * @param path The path to the variable to inspect, as an array of access + * keys. + * + * @returns An inspected variable. + */ + inspect(path: Array): Promise { + return super.performRpc('inspect', ['path'], [path]); + } + /** + * Format for clipboard + * + * Requests a formatted representation of a variable for copying to the + * clipboard. + * + * @param path The path to the variable to format, as an array of access + * keys. + * @param format The requested format for the variable, as a MIME type + * + * @returns An object formatted for copying to the clipboard. + */ + clipboardFormat(path: Array, format: string): Promise { + return super.performRpc('clipboard_format', ['path', 'format'], [path, format]); + } +} + From 25c286b59f3c5691159a7a94c74aec1c7d944e4e Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Fri, 15 Dec 2023 15:02:21 -0800 Subject: [PATCH 14/71] generate schemas for each component --- positron/comms/generate-comms.ts | 172 ++++++++++++------ positron/comms/variables-backend-openrpc.json | 1 + .../common/positronVariablesComm.ts | 61 +++++++ 3 files changed, 182 insertions(+), 52 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 9f59fcfaa4a..4e7ef541ebc 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -132,6 +132,24 @@ function formatComment(leader: string, comment: string): string { return result; } +function* createRustStruct(name: string, description: string, properties: Record): Generator { + yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; + yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; + const props = Object.keys(properties); + for (let i = 0; i < props.length; i++) { + const prop = props[i]; + const schema = properties[prop]; + if (schema.description) { + yield formatComment('\t/// ', schema.description); + } + yield `\tpub ${prop}: ${RustTypeMap[schema.type]},\n`; + if (i < props.length - 1) { + yield '\n'; + } + } + yield '}\n\n'; +} + function* createRustComm(name: string, frontend: any, backend: any): Generator { yield `/*--------------------------------------------------------------------------------------------- * Copyright (C) ${year} Posit Software, PBC. All rights reserved. @@ -152,21 +170,22 @@ use serde::Serialize; if (method.result && method.result.schema && method.result.schema.type === 'object') { - yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; - yield `pub struct ${snakeCaseToSentenceCase(method.result.schema.name)} {\n`; - const props = Object.keys(method.result.schema.properties); - for (let i = 0; i < props.length; i++) { - const prop = props[i]; - const schema = method.result.schema.properties[prop]; - if (schema.description) { - yield formatComment('\t/// ', schema.description); - } - yield `\tpub ${prop}: ${RustTypeMap[schema.type]},\n`; - if (i < props.length - 1) { - yield '\n'; - } - } - yield '}\n\n'; + yield* createRustStruct(method.result.schema.name, + method.result.schema.description, + method.result.schema.properties); + } + } + } + + for (const source of [backend, frontend]) { + if (!source) { + continue; + } + if (source.components && source.components.schemas) { + for (const schema of Object.keys(backend.components.schemas)) { + yield* createRustStruct(schema, + backend.components.schemas[schema].description, + backend.components.schemas[schema].properties); } } } @@ -176,6 +195,14 @@ use serde::Serialize; if (!source) { continue; } + if (source.components && source.components.schemas) { + for (const schema of Object.keys(backend.components.schemas)) { + yield* createRustStruct(schema, + backend.components.schemas[schema].description, + backend.components.schemas[schema].properties); + } + } + for (const method of source.methods) { for (const param of method.params) { if (param.schema.enum) { @@ -256,7 +283,7 @@ use serde::Serialize; if (method.params.length > 0) { yield `(${snakeCaseToSentenceCase(method.name)}Params),\n`; } else { - yield ',\n\n'; + yield ',\n'; } } yield `}\n\n`; @@ -304,6 +331,28 @@ use serde::Serialize; } } +function* createPythonDataclass(name: string, description: string, properties: Record): Generator { + yield '@dataclass\n'; + yield `class ${snakeCaseToSentenceCase(name)}:\n`; + if (description) { + yield ' """\n'; + yield formatComment(' ', description); + yield ' """\n'; + yield '\n'; + } + for (const prop of Object.keys(properties)) { + const schema = properties[prop]; + yield ` ${prop}: ${PythonTypeMap[schema.type]}`; + yield ' = field(\n'; + yield ` metadata={\n`; + yield ` "description": "${schema.description}",\n`; + yield ` }\n`; + yield ` )\n\n`; + } + yield '\n\n'; +} + + function* createPythonComm(name: string, frontend: any, backend: any): Generator { yield `# # Copyright (C) ${year} Posit Software, PBC. All rights reserved. @@ -324,24 +373,22 @@ from dataclasses import dataclass, field if (method.result && method.result.schema && method.result.schema.type === 'object') { - yield '@dataclass\n'; - yield `class ${snakeCaseToSentenceCase(method.result.schema.name)}:\n`; - if (method.result.schema.description) { - yield ' """\n'; - yield formatComment(' ', method.result.schema.description); - yield ' """\n'; - yield '\n'; - } - for (const prop of Object.keys(method.result.schema.properties)) { - const schema = method.result.schema.properties[prop]; - yield ` ${prop}: ${PythonTypeMap[schema.type]}`; - yield ' = field(\n'; - yield ` metadata={\n`; - yield ` "description": "${schema.description}",\n`; - yield ` }\n`; - yield ` )\n\n`; - } - yield '\n\n'; + yield* createPythonDataclass(method.result.schema.name, + method.result.schema.description, + method.result.schema.properties); + } + } + } + + for (const source of [backend, frontend]) { + if (!source) { + continue; + } + if (source.components && source.components.schemas) { + for (const schema of Object.keys(backend.components.schemas)) { + yield* createPythonDataclass(schema, + backend.components.schemas[schema].description, + backend.components.schemas[schema].properties); } } } @@ -493,6 +540,29 @@ from dataclasses import dataclass, field } } +async function* createTypescriptInterface(name: string, + description: string, properties: Record) { + + yield '/**\n'; + yield formatComment(' * ', description); + yield ' */\n'; + yield `export interface ${snakeCaseToSentenceCase(name)} {\n`; + for (const prop of Object.keys(properties)) { + const schema = properties[prop]; + yield '\t/**\n'; + yield formatComment('\t * ', schema.description); + yield '\t */\n'; + yield `\t${prop}: `; + if (schema.type === 'object') { + yield snakeCaseToSentenceCase(schema.name); + } else { + yield TypescriptTypeMap[schema.type]; + } + yield `;\n\n`; + } + yield '}\n\n'; +} + async function* createTypescriptComm(name: string, frontend: any, backend: any): AsyncGenerator { // Read the metadata file const metadata: CommMetadata = JSON.parse( @@ -517,24 +587,22 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (method.result && method.result.schema && method.result.schema.type === 'object') { - yield '/**\n'; - yield formatComment(' * ', method.result.schema.description); - yield ' */\n'; - yield `export interface ${snakeCaseToSentenceCase(method.result.schema.name)} {\n`; - for (const prop of Object.keys(method.result.schema.properties)) { - const schema = method.result.schema.properties[prop]; - yield '\t/**\n'; - yield formatComment('\t * ', schema.description); - yield '\t */\n'; - yield `\t${prop}: `; - if (schema.type === 'object') { - yield snakeCaseToSentenceCase(schema.name); - } else { - yield TypescriptTypeMap[schema.type]; - } - yield `;\n\n`; - } - yield '}\n\n'; + yield* createTypescriptInterface(method.result.schema.name, + method.result.schema.description, + method.result.schema.properties); + } + } + } + + for (const source of [backend, frontend]) { + if (!source) { + continue; + } + if (source.components && source.components.schemas) { + for (const schema of Object.keys(backend.components.schemas)) { + yield* createTypescriptInterface(schema, + backend.components.schemas[schema].description, + backend.components.schemas[schema].properties); } } } diff --git a/positron/comms/variables-backend-openrpc.json b/positron/comms/variables-backend-openrpc.json index 1e69ed8b5d7..45224c69e8c 100644 --- a/positron/comms/variables-backend-openrpc.json +++ b/positron/comms/variables-backend-openrpc.json @@ -121,6 +121,7 @@ "schemas": { "variable": { "type": "object", + "description": "A single variable in the runtime.", "properties": { "access_key": { "type": "string", diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index 44bd1702815..be0e318624d 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -44,6 +44,67 @@ export interface FormattedVariable { } +/** + * A single variable in the runtime. + */ +export interface Variable { + /** + * A key that uniquely identifies the variable within the runtime and can + * be used to access the variable in `inspect` requests + */ + access_key: string; + + /** + * The name of the variable, formatted for display + */ + display_name: string; + + /** + * A string representation of the variable's value, formatted for display + * and possibly truncated + */ + display_value: string; + + /** + * The variable's type, formatted for display + */ + display_type: string; + + /** + * Extended information about the variable's type + */ + type_info: string; + + /** + * The kind of value the variable represents, such as 'string' or + * 'number' + */ + kind: string; + + /** + * The number of elements in the variable, if it is a collection + */ + length: number; + + /** + * Whether the variable has child variables + */ + has_children: boolean; + + /** + * True if there is a viewer available for this variable (i.e. the + * runtime can handle a 'view' request for this variable) + */ + has_viewer: boolean; + + /** + * True the 'value' field is a truncated representation of the variable's + * value + */ + is_truncated: boolean; + +} + export class PositronVariablesComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); From fefd27a10fde94c5afb88f6da14802b633fcae60 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Fri, 15 Dec 2023 16:47:06 -0800 Subject: [PATCH 15/71] type derivation for refs and containers (WIP) --- positron/comms/generate-comms.ts | 88 +++++++++++++------ .../common/positronVariablesComm.ts | 8 +- 2 files changed, 66 insertions(+), 30 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 4e7ef541ebc..0c480393170 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -94,6 +94,49 @@ function snakeCaseToSentenceCase(name: string) { return snakeCaseToCamelCase(name).replace(/^[a-z]/, (m) => m[0].toUpperCase()); } +/** + * Parse a ref tag + * + * @param ref The ref to parse + * @returns The name of the object referred to by the ref + */ +function parseRef(ref: string, contract: any): string { + const parts = ref.split('/'); + let target = contract; + for (let i = 0; i < parts.length; i++) { + if (parts[i] === '#') { + continue; + } + if (Object.keys(target).includes(parts[i])) { + target = target[parts[i]]; + } else { + throw new Error(`Invalid ref: ${ref} (part '${parts[i]}' not found)`); + } + } + if (!target.name) { + throw new Error(`Invalid ref: ${ref} (no name found)`); + } + return snakeCaseToSentenceCase(target.name); +} + +function deriveType(contract: any, typeMap: Record, schema: any): string { + if (schema.type === 'array') { + if (schema.items.type === 'object' && schema.items.$ref) { + return `${typeMap['array']}<${parseRef(schema.items.$ref, contract)}>`; + } else { + return `${typeMap['array']}<${typeMap[schema.items.type]}>`; + } + } else if (schema.type === 'object' && schema.$ref) { + return parseRef(schema.$ref, contract); + } else { + if (Object.keys(typeMap).includes(schema.type)) { + return typeMap[schema.type]; + } else { + throw new Error(`Unknown type: ${schema.type}`); + } + } +} + // Breaks a single line of text into multiple lines, each of which is no longer than // 70 characters. function formatLines(line: string): string[] { @@ -132,7 +175,8 @@ function formatComment(leader: string, comment: string): string { return result; } -function* createRustStruct(name: string, description: string, properties: Record): Generator { +function* createRustStruct(contract: any, name: string, description: string, properties: Record): Generator { + yield formatComment('/// ', description); yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; const props = Object.keys(properties); @@ -142,7 +186,7 @@ function* createRustStruct(name: string, description: string, properties: Record if (schema.description) { yield formatComment('\t/// ', schema.description); } - yield `\tpub ${prop}: ${RustTypeMap[schema.type]},\n`; + yield `\tpub ${prop}: ${deriveType(contract, RustTypeMap, schema)},\n`; if (i < props.length - 1) { yield '\n'; } @@ -170,7 +214,7 @@ use serde::Serialize; if (method.result && method.result.schema && method.result.schema.type === 'object') { - yield* createRustStruct(method.result.schema.name, + yield* createRustStruct(backend, method.result.schema.name, method.result.schema.description, method.result.schema.properties); } @@ -183,7 +227,7 @@ use serde::Serialize; } if (source.components && source.components.schemas) { for (const schema of Object.keys(backend.components.schemas)) { - yield* createRustStruct(schema, + yield* createRustStruct(source, schema, backend.components.schemas[schema].description, backend.components.schemas[schema].properties); } @@ -195,14 +239,6 @@ use serde::Serialize; if (!source) { continue; } - if (source.components && source.components.schemas) { - for (const schema of Object.keys(backend.components.schemas)) { - yield* createRustStruct(schema, - backend.components.schemas[schema].description, - backend.components.schemas[schema].properties); - } - } - for (const method of source.methods) { for (const param of method.params) { if (param.schema.enum) { @@ -252,7 +288,7 @@ use serde::Serialize; yield `\tpub ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)},\n`; } else { // Otherwise use the type directly - yield `\tpub ${param.name}: ${RustTypeMap[param.schema.type]},\n`; + yield `\tpub ${param.name}: ${deriveType(source, RustTypeMap, param.schema)},\n`; } if (i < method.params.length - 1) { yield '\n'; @@ -331,7 +367,7 @@ use serde::Serialize; } } -function* createPythonDataclass(name: string, description: string, properties: Record): Generator { +function* createPythonDataclass(contract: any, name: string, description: string, properties: Record): Generator { yield '@dataclass\n'; yield `class ${snakeCaseToSentenceCase(name)}:\n`; if (description) { @@ -342,7 +378,7 @@ function* createPythonDataclass(name: string, description: string, properties: R } for (const prop of Object.keys(properties)) { const schema = properties[prop]; - yield ` ${prop}: ${PythonTypeMap[schema.type]}`; + yield ` ${prop}: ${deriveType(contract, PythonTypeMap, schema)}`; yield ' = field(\n'; yield ` metadata={\n`; yield ` "description": "${schema.description}",\n`; @@ -373,7 +409,7 @@ from dataclasses import dataclass, field if (method.result && method.result.schema && method.result.schema.type === 'object') { - yield* createPythonDataclass(method.result.schema.name, + yield* createPythonDataclass(backend, method.result.schema.name, method.result.schema.description, method.result.schema.properties); } @@ -386,7 +422,7 @@ from dataclasses import dataclass, field } if (source.components && source.components.schemas) { for (const schema of Object.keys(backend.components.schemas)) { - yield* createPythonDataclass(schema, + yield* createPythonDataclass(source, schema, backend.components.schemas[schema].description, backend.components.schemas[schema].properties); } @@ -456,7 +492,7 @@ from dataclasses import dataclass, field if (param.schema.enum) { yield ` ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield ` ${param.name}: ${PythonTypeMap[param.schema.type]}`; + yield ` ${param.name}: ${deriveType(backend, PythonTypeMap, param.schema)}`; } yield ' = field(\n'; yield ` metadata={\n`; @@ -527,7 +563,7 @@ from dataclasses import dataclass, field if (param.schema.enum) { yield ` ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield ` ${param.name}: ${PythonTypeMap[param.schema.type]}`; + yield ` ${param.name}: ${deriveType(backend, PythonTypeMap, param.schema)}`; } yield ' = field(\n'; yield ` metadata={\n`; @@ -540,7 +576,7 @@ from dataclasses import dataclass, field } } -async function* createTypescriptInterface(name: string, +async function* createTypescriptInterface(contract: any, name: string, description: string, properties: Record) { yield '/**\n'; @@ -556,7 +592,7 @@ async function* createTypescriptInterface(name: string, if (schema.type === 'object') { yield snakeCaseToSentenceCase(schema.name); } else { - yield TypescriptTypeMap[schema.type]; + yield deriveType(contract, TypescriptTypeMap, schema); } yield `;\n\n`; } @@ -587,7 +623,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (method.result && method.result.schema && method.result.schema.type === 'object') { - yield* createTypescriptInterface(method.result.schema.name, + yield* createTypescriptInterface(backend, method.result.schema.name, method.result.schema.description, method.result.schema.properties); } @@ -600,7 +636,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co } if (source.components && source.components.schemas) { for (const schema of Object.keys(backend.components.schemas)) { - yield* createTypescriptInterface(schema, + yield* createTypescriptInterface(backend, schema, backend.components.schemas[schema].description, backend.components.schemas[schema].properties); } @@ -625,7 +661,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (param.schema.type === 'string' && param.schema.enum) { yield param.schema.enum.map((value: string) => `'${value}'`).join(' | '); } else { - yield TypescriptTypeMap[param.schema.type as string]; + yield deriveType(frontend, TypescriptTypeMap, param.schema); } yield `;\n\n`; } @@ -686,7 +722,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co const param = method.params[i]; yield snakeCaseToCamelCase(param.name) + ': ' + - TypescriptTypeMap[param.schema.type as string]; + deriveType(backend, TypescriptTypeMap, param.schema); if (i < method.params.length - 1) { yield ', '; } @@ -696,7 +732,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (method.result.schema.type === 'object') { yield snakeCaseToSentenceCase(method.result.schema.name); } else { - yield TypescriptTypeMap[method.result.schema.type as string]; + yield deriveType(backend, TypescriptTypeMap, method.result.schema); } } yield '> {\n'; diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index be0e318624d..efe16b4e282 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -17,7 +17,7 @@ export interface InspectedVariable { /** * The children of the inspected variable. */ - children: Array; + children: Array; /** * The total number of children. This may be greater than the number of @@ -130,7 +130,7 @@ export class PositronVariablesComm extends PositronBaseComm { * @param names The names of the variables to delete. * */ - delete(names: Array): Promise<> { + delete(names: Array): Promise<> { return super.performRpc('delete', ['names'], [names]); } /** @@ -143,7 +143,7 @@ export class PositronVariablesComm extends PositronBaseComm { * * @returns An inspected variable. */ - inspect(path: Array): Promise { + inspect(path: Array): Promise { return super.performRpc('inspect', ['path'], [path]); } /** @@ -158,7 +158,7 @@ export class PositronVariablesComm extends PositronBaseComm { * * @returns An object formatted for copying to the clipboard. */ - clipboardFormat(path: Array, format: string): Promise { + clipboardFormat(path: Array, format: string): Promise { return super.performRpc('clipboard_format', ['path', 'format'], [path, format]); } } From 02b5ffcedbf50a2e52f6bdd59016a7d35132ec47 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Fri, 15 Dec 2023 16:54:52 -0800 Subject: [PATCH 16/71] parse refs for non-object types --- positron/comms/generate-comms.ts | 2 +- positron/comms/variables-backend-openrpc.json | 1 + .../services/languageRuntime/common/positronVariablesComm.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 0c480393170..2f9e9854a57 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -121,7 +121,7 @@ function parseRef(ref: string, contract: any): string { function deriveType(contract: any, typeMap: Record, schema: any): string { if (schema.type === 'array') { - if (schema.items.type === 'object' && schema.items.$ref) { + if (schema.items.$ref) { return `${typeMap['array']}<${parseRef(schema.items.$ref, contract)}>`; } else { return `${typeMap['array']}<${typeMap[schema.items.type]}>`; diff --git a/positron/comms/variables-backend-openrpc.json b/positron/comms/variables-backend-openrpc.json index 45224c69e8c..2b92c27ca66 100644 --- a/positron/comms/variables-backend-openrpc.json +++ b/positron/comms/variables-backend-openrpc.json @@ -121,6 +121,7 @@ "schemas": { "variable": { "type": "object", + "name": "variable", "description": "A single variable in the runtime.", "properties": { "access_key": { diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index efe16b4e282..f70cb337fc1 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -17,7 +17,7 @@ export interface InspectedVariable { /** * The children of the inspected variable. */ - children: Array; + children: Array; /** * The total number of children. This may be greater than the number of From 03044e0c156b231701371c294e0b01e734545b78 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Mon, 18 Dec 2023 17:41:25 -0800 Subject: [PATCH 17/71] add data tool backend example; handle object schema only; better errors --- positron/comms/data_tool-backend-openrpc.json | 427 ++++++++++++++++++ positron/comms/data_tool.json | 9 + positron/comms/generate-comms.ts | 28 +- 3 files changed, 456 insertions(+), 8 deletions(-) create mode 100644 positron/comms/data_tool-backend-openrpc.json create mode 100644 positron/comms/data_tool.json diff --git a/positron/comms/data_tool-backend-openrpc.json b/positron/comms/data_tool-backend-openrpc.json new file mode 100644 index 00000000000..37e78c06560 --- /dev/null +++ b/positron/comms/data_tool-backend-openrpc.json @@ -0,0 +1,427 @@ +{ + "openrpc": "1.3.0", + "info": { + "title": "Data Tool Backend", + "version": "1.0.0" + }, + "methods": [ + { + "name": "get_schema", + "summary": "Request full schema for a table-like object", + "params": [], + "result": { + "schema": { + "type": "object", + "name": "table_schema", + "description": "The schema for a table-like object", + "properties": { + "columns": { + "type": "array", + "description": "Schema for each column in the table", + "items": { + "$ref": "#/components/schemas/column_schema" + } + }, + "num_rows": { + "type": "integer", + "description": "Numbers of rows in the unfiltered dataset" + } + } + } + } + }, + { + "name": "get_data_values", + "summary": "Request a rectangular subset of data with values formatted as strings", + "params": [ + { + "name": "row_start_index", + "description": "First row to fetch (inclusive)", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "row_end_index", + "description": "Last row to fetch (inclusive). May be beyond end", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "column_start_index", + "description": "First column to fetch (inclusive)", + "schema": { + "type": "integer" + } + }, + { + "name": "column_end_index", + "description": "Last column to fetch (inclusive). May extend beyond end", + "schema": { + "type": "integer" + } + } + ], + "result": { + "schema": { + "type": "object", + "name": "table_data", + "description": "Table values formatted as strings", + "required": [ + "columns" + ], + "properties": { + "columns": { + "type": "array", + "description": "The columns of data", + "items": { + "$ref": "#/components/schemas/column_formatted_data" + } + }, + "row_labels": { + "type": "array", + "description": "Zero or more arrays of row labels", + "items": { + "$ref": "#/components/schemas/column_formatted_data" + } + } + } + } + } + }, + { + "name": "set_column_filters", + "summary": "Set or clear column filters on table, replacing any previous filters", + "params": [ + { + "name": "filters", + "description": "Zero or more filters to apply", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/column_filter" + } + } + } + ], + "result": { + "schema": { + "type": "object", + "name": "filter_result", + "description": "The result of applying filters to a table", + "properties": { + "selected_num_rows": { + "type": "integer", + "description": "Number of rows in table after applying filters" + } + } + } + } + }, + { + "name": "set_sort_columns", + "summary": "Set or clear sort-by-column(s)", + "params": [ + { + "name": "sort_keys", + "description": "Pass zero or more keys to sort by. Clears any existing keys", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/column_sort_key" + } + } + } + ], + "result": {} + }, + { + "name": "get_column_profile", + "summary": "Requests a statistical summary or data profile for a column", + "params": [ + { + "name": "profile_id", + "description": "Unique identifier for the requested profile", + "schema": { + "type": "string" + } + }, + { + "name": "profile_type", + "description": "The type of analytical column profile", + "schema": { + "type": "string", + "enum": [ + "freqtable", + "histogram" + ] + } + }, + { + "name": "column", + "description": "Column name to compute profile for", + "schema": { + "type": "string" + } + } + ], + "result": { + "schema": { + "type": "object", + "name": "profile_result", + "description": "Result of computing column profile", + "required": [ + "null_count" + ], + "properties": { + "null_count": { + "type": "integer", + "description": "Number of null values in column" + }, + "min_value": { + "type": "string", + "description": "Minimum value as string computed as part of histogram" + }, + "max_value": { + "type": "string", + "description": "Maximum value as string computed as part of histogram" + }, + "mean_value": { + "type": "string", + "description": "Average value as string computed as part of histogram" + }, + "histogram_bin_sizes": { + "type": "array", + "description": "Absolute count of values in each histogram bin", + "items": { + "type": "integer" + } + }, + "histogram_bin_width": { + "type": "number", + "description": "Absolute floating-point width of a histogram bin" + }, + "histogram_quantiles": { + "type": "array", + "description": "Quantile values computed from histogram bins", + "items": { + "$ref": "#/components/schemas/column_quantile_value" + } + }, + "freqtable_counts": { + "type": "array", + "description": "Counts of distinct values in column", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Stringified value" + }, + "count": { + "type": "integer", + "description": "Number of occurrences of value" + } + } + } + }, + "freqtable_other_count": { + "type": "integer", + "description": "Number of other values not accounted for in counts" + } + } + } + } + }, + { + "name": "get_state", + "summary": "Request the current backend state (applied filters and sort columns)", + "params": [], + "result": { + "schema": { + "type": "object", + "name": "backend_state", + "description": "The current backend state", + "properties": { + "filters": { + "type": "array", + "description": "The set of currently applied filters", + "items": { + "$ref": "#/components/schemas/column_filter" + } + }, + "sort_keys": { + "type": "array", + "description": "The set of currently applied sorts", + "items": { + "$ref": "#/components/schemas/column_sort_key" + } + } + } + } + } + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "column_schema": { + "type": "object", + "description": "Schema for a column in a table", + "required": [ + "name", + "type_name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of column as UTF-8 string" + }, + "type_name": { + "type": "string", + "description": "Canonical name of data type class" + }, + "description": { + "type": "string", + "description": "Column annotation / description" + }, + "children": { + "type": "array", + "description": "Schema of nested child types", + "items": { + "$ref": "#/components/schemas/column_schema" + } + }, + "precision": { + "type": "integer", + "description": "Precision for decimal types" + }, + "scale": { + "type": "integer", + "description": "Scale for decimal types" + }, + "timezone": { + "type": "string", + "description": "Time zone for timestamp with time zone" + }, + "type_size": { + "type": "integer", + "description": "Size parameter for fixed-size types (list, binary)" + } + } + }, + "column_formatted_data": { + "type": "array", + "description": "Column values formatted as strings", + "items": { + "type": "string" + } + }, + "column_filter": { + "type": "object", + "description": "Specifies a table row filter based on a column's values", + "required": [ + "filter_id", + "filter_type" + ], + "properties": { + "filter_id": { + "type": "string", + "description": "Unique identifier for this filter" + }, + "filter_type": { + "type": "string", + "description": "Type of filter to apply", + "enum": [ + "isnull", + "notnull", + "compare", + "set_membership", + "search" + ] + }, + "compare_op": { + "type": "string", + "description": "String representation of a binary comparison", + "enum": [ + "==", + "!=", + "<", + "<=", + ">", + ">=" + ] + }, + "compare_value": { + "type": "string", + "description": "A stringified column value for a comparison filter" + }, + "set_member_values": { + "type": "array", + "description": "Array of column values for a set membership filter", + "items": { + "type": "string" + } + }, + "set_member_inclusive": { + "type": "boolean", + "description": "Filter by including only values passed (true) or excluding (false)" + }, + "search_type": { + "type": "string", + "description": "Type of search to perform", + "enum": [ + "contains", + "startswith", + "endswith", + "regex" + ] + }, + "search_term": { + "type": "string", + "description": "String value/regex to search for in stringified data" + }, + "search_case_sensitive": { + "type": "boolean", + "description": "If true, do a case-sensitive search, otherwise case-insensitive" + } + } + }, + "column_quantile_value": { + "type": "object", + "description": "An exact or approximate quantile value from a column", + "properties": { + "q": { + "type": "number", + "description": "Quantile number (percentile). E.g. 1 for 1%, 50 for median" + }, + "value": { + "type": "string", + "description": "Stringified quantile value" + }, + "exact": { + "type": "boolean", + "description": "Whether value is exact or approximate (computed from binned data or sketches)" + } + } + }, + "column_sort_key": { + "type": "object", + "description": "Specifies a column to sort by", + "properties": { + "column": { + "type": "string", + "description": "Column name to sort by" + }, + "ascending": { + "type": "boolean", + "description": "Sort order, ascending (true) or descending (false)" + } + } + } + } + } +} diff --git a/positron/comms/data_tool.json b/positron/comms/data_tool.json new file mode 100644 index 00000000000..94d8d74e869 --- /dev/null +++ b/positron/comms/data_tool.json @@ -0,0 +1,9 @@ +{ + "name": "data_tool", + "initiator": "frontend", + "initial_data": { + "schema": { + "type": "null" + } + } +} diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 2f9e9854a57..2afaeb0d872 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -113,10 +113,7 @@ function parseRef(ref: string, contract: any): string { throw new Error(`Invalid ref: ${ref} (part '${parts[i]}' not found)`); } } - if (!target.name) { - throw new Error(`Invalid ref: ${ref} (no name found)`); - } - return snakeCaseToSentenceCase(target.name); + return snakeCaseToSentenceCase(parts[parts.length - 1]); } function deriveType(contract: any, typeMap: Record, schema: any): string { @@ -579,12 +576,21 @@ from dataclasses import dataclass, field async function* createTypescriptInterface(contract: any, name: string, description: string, properties: Record) { + if (!description) { + throw new Error(`No description for '${name}'; please add a description to the schema`); + } yield '/**\n'; yield formatComment(' * ', description); yield ' */\n'; yield `export interface ${snakeCaseToSentenceCase(name)} {\n`; + if (!properties || Object.keys(properties).length === 0) { + throw new Error(`No properties for '${name}'; please add properties to the schema`); + } for (const prop of Object.keys(properties)) { const schema = properties[prop]; + if (!schema.description) { + throw new Error(`No description for the '${name}.${prop}' value; please add a description to the schema`); + } yield '\t/**\n'; yield formatComment('\t * ', schema.description); yield '\t */\n'; @@ -635,10 +641,13 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co continue; } if (source.components && source.components.schemas) { - for (const schema of Object.keys(backend.components.schemas)) { - yield* createTypescriptInterface(backend, schema, - backend.components.schemas[schema].description, - backend.components.schemas[schema].properties); + for (const key of Object.keys(backend.components.schemas)) { + const schema = backend.components.schemas[key]; + if (schema.type === 'object') { + yield* createTypescriptInterface(backend, key, + schema.description, + schema.properties); + } } } } @@ -720,6 +729,9 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co yield '\t' + snakeCaseToCamelCase(method.name) + '('; for (let i = 0; i < method.params.length; i++) { const param = method.params[i]; + if (!param.schema) { + throw new Error(`No schema for '${method.name}' parameter '${param.name}'`); + } yield snakeCaseToCamelCase(param.name) + ': ' + deriveType(backend, TypescriptTypeMap, param.schema); From 35ed71466da14cff620e97dc646055a67bb18a9a Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Mon, 18 Dec 2023 17:53:02 -0800 Subject: [PATCH 18/71] add description to all summary values; more error work --- positron/comms/data_tool-backend-openrpc.json | 16 ++++++++---- positron/comms/generate-comms.ts | 25 +++++++++++++------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/positron/comms/data_tool-backend-openrpc.json b/positron/comms/data_tool-backend-openrpc.json index 37e78c06560..0d2408e4b55 100644 --- a/positron/comms/data_tool-backend-openrpc.json +++ b/positron/comms/data_tool-backend-openrpc.json @@ -7,7 +7,8 @@ "methods": [ { "name": "get_schema", - "summary": "Request full schema for a table-like object", + "summary": "Request schema", + "description": "Request full schema for a table-like object", "params": [], "result": { "schema": { @@ -32,7 +33,8 @@ }, { "name": "get_data_values", - "summary": "Request a rectangular subset of data with values formatted as strings", + "summary": "Get a rectangle of data values", + "description": "Request a rectangular subset of data with values formatted as strings", "params": [ { "name": "row_start_index", @@ -94,7 +96,8 @@ }, { "name": "set_column_filters", - "summary": "Set or clear column filters on table, replacing any previous filters", + "summary": "Set column filters", + "description": "Set or clear column filters on table, replacing any previous filters", "params": [ { "name": "filters", @@ -124,6 +127,7 @@ { "name": "set_sort_columns", "summary": "Set or clear sort-by-column(s)", + "description": "Set or clear the columns(s) to sort by, replacing any previous sort columns", "params": [ { "name": "sort_keys", @@ -140,7 +144,8 @@ }, { "name": "get_column_profile", - "summary": "Requests a statistical summary or data profile for a column", + "summary": "Get a column profile", + "description": "Requests a statistical summary or data profile for a column", "params": [ { "name": "profile_id", @@ -238,7 +243,8 @@ }, { "name": "get_state", - "summary": "Request the current backend state (applied filters and sort columns)", + "summary": "Get the state", + "description": "Request the current backend state (applied filters and sort columns)", "params": [], "result": { "schema": { diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 2afaeb0d872..af534cd63b3 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -223,10 +223,13 @@ use serde::Serialize; continue; } if (source.components && source.components.schemas) { - for (const schema of Object.keys(backend.components.schemas)) { - yield* createRustStruct(source, schema, - backend.components.schemas[schema].description, - backend.components.schemas[schema].properties); + for (const key of Object.keys(backend.components.schemas)) { + const schema = backend.components.schemas[key]; + if (schema.type === 'object') { + yield* createRustStruct(backend, key, + schema.description, + schema.properties); + } } } } @@ -418,10 +421,13 @@ from dataclasses import dataclass, field continue; } if (source.components && source.components.schemas) { - for (const schema of Object.keys(backend.components.schemas)) { - yield* createPythonDataclass(source, schema, - backend.components.schemas[schema].description, - backend.components.schemas[schema].properties); + for (const key of Object.keys(backend.components.schemas)) { + const schema = backend.components.schemas[key]; + if (schema.type === 'object') { + yield* createPythonDataclass(backend, key, + schema.description, + schema.properties); + } } } } @@ -479,6 +485,9 @@ from dataclasses import dataclass, field if (backend) { for (const method of backend.methods) { + if (!method.description) { + throw new Error(`No description for '${method.name}'; please add a description to the schema`); + } yield `@dataclass\n`; yield `class ${snakeCaseToSentenceCase(method.name)}Params:\n`; yield ` """\n`; From d5dfbaf8c0c840db99f957e1edd43784379af654 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Mon, 18 Dec 2023 19:31:34 -0800 Subject: [PATCH 19/71] use a language-specific array designation --- positron/comms/generate-comms.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index af534cd63b3..3cc2440912b 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -48,7 +48,8 @@ const TypescriptTypeMap: Record = { 'number': 'number', 'string': 'string', 'null': 'null', - 'array': 'Array', + 'array-begin': 'Array<', + 'array-end': '>', 'object': 'object', }; @@ -59,7 +60,8 @@ const RustTypeMap: Record = { 'number': 'f64', 'string': 'String', 'null': 'null', - 'array': 'Vec', + 'array-begin': 'Vec<', + 'array-end': '>', 'object': 'HashMap', }; @@ -70,7 +72,8 @@ const PythonTypeMap: Record = { 'number': 'float', 'string': 'str', 'null': 'null', - 'array': 'List', + 'array-begin': 'List[', + 'array-end': ']', 'object': 'Dict', }; @@ -119,9 +122,13 @@ function parseRef(ref: string, contract: any): string { function deriveType(contract: any, typeMap: Record, schema: any): string { if (schema.type === 'array') { if (schema.items.$ref) { - return `${typeMap['array']}<${parseRef(schema.items.$ref, contract)}>`; + return typeMap['array-begin'] + + parseRef(schema.items.$ref, contract) + + typeMap['array-end']; } else { - return `${typeMap['array']}<${typeMap[schema.items.type]}>`; + return typeMap['array-begin'] + + typeMap[schema.items.type] + + typeMap['array-end']; } } else if (schema.type === 'object' && schema.$ref) { return parseRef(schema.$ref, contract); From 4db6a257126350a2f4fc99d3efb3d3dc5d399f7d Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Mon, 18 Dec 2023 20:12:00 -0800 Subject: [PATCH 20/71] typescript: create type aliases for non-object schemas --- positron/comms/generate-comms.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 3cc2440912b..20871c18d09 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -663,6 +663,13 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co yield* createTypescriptInterface(backend, key, schema.description, schema.properties); + } else { + yield `/**\n`; + yield formatComment(' * ', schema.description); + yield ' */\n'; + yield `export type ${snakeCaseToSentenceCase(key)} = `; + yield deriveType(source, TypescriptTypeMap, schema); + yield ';\n\n'; } } } From 7fcbfcb627ef35c53fef6ae0fa21f25a39bb8798 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Mon, 18 Dec 2023 20:13:43 -0800 Subject: [PATCH 21/71] use a void promise when no result is expected --- positron/comms/generate-comms.ts | 4 +++- .../services/languageRuntime/common/positronHelpComm.ts | 1 + .../services/languageRuntime/common/positronPlotComm.ts | 1 + .../languageRuntime/common/positronVariablesComm.ts | 8 ++++++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 20871c18d09..5e16398e57b 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -769,6 +769,8 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co } else { yield deriveType(backend, TypescriptTypeMap, method.result.schema); } + } else { + yield 'void'; } yield '> {\n'; yield '\t\treturn super.performRpc(\'' + method.name + '\', ['; @@ -786,7 +788,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co } } yield ']);\n'; - yield `\t}\n`; + yield `\t}\n\n`; } } diff --git a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts index 3facbec7aee..79344a03c3b 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts @@ -54,6 +54,7 @@ export class PositronHelpComm extends PositronBaseComm { return super.performRpc('show_help_topic', ['topic'], [topic]); } + /** * Request to show help in the frontend */ diff --git a/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts b/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts index 1bb8e08fd21..339102be28b 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts @@ -54,6 +54,7 @@ export class PositronPlotComm extends PositronBaseComm { return super.performRpc('render', ['height', 'width', 'pixel_ratio'], [height, width, pixelRatio]); } + /** * Notification that a plot has been updated on the backend. */ diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index f70cb337fc1..e81cfc5038a 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -119,9 +119,10 @@ export class PositronVariablesComm extends PositronBaseComm { * addition to normal variables * */ - clear(includeHiddenObjects: boolean): Promise<> { + clear(includeHiddenObjects: boolean): Promise { return super.performRpc('clear', ['include_hidden_objects'], [includeHiddenObjects]); } + /** * Deletes a set of named variables * @@ -130,9 +131,10 @@ export class PositronVariablesComm extends PositronBaseComm { * @param names The names of the variables to delete. * */ - delete(names: Array): Promise<> { + delete(names: Array): Promise { return super.performRpc('delete', ['names'], [names]); } + /** * Inspect a variable * @@ -146,6 +148,7 @@ export class PositronVariablesComm extends PositronBaseComm { inspect(path: Array): Promise { return super.performRpc('inspect', ['path'], [path]); } + /** * Format for clipboard * @@ -161,5 +164,6 @@ export class PositronVariablesComm extends PositronBaseComm { clipboardFormat(path: Array, format: string): Promise { return super.performRpc('clipboard_format', ['path', 'format'], [path, format]); } + } From 0e71d02ee27a031d9adb1f63d077595541684797 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Mon, 18 Dec 2023 20:21:22 -0800 Subject: [PATCH 22/71] initial compilation of data tool schema --- .../common/positronDataToolComm.ts | 352 ++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts new file mode 100644 index 00000000000..0645802bfb7 --- /dev/null +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -0,0 +1,352 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// +// AUTO-GENERATED from data_tool.json; do not edit. +// + +import { Event } from 'vs/base/common/event'; +import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; +import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; + +/** + * The schema for a table-like object + */ +export interface TableSchema { + /** + * Schema for each column in the table + */ + columns: Array; + + /** + * Numbers of rows in the unfiltered dataset + */ + num_rows: number; + +} + +/** + * Table values formatted as strings + */ +export interface TableData { + /** + * The columns of data + */ + columns: Array; + + /** + * Zero or more arrays of row labels + */ + row_labels: Array; + +} + +/** + * The result of applying filters to a table + */ +export interface FilterResult { + /** + * Number of rows in table after applying filters + */ + selected_num_rows: number; + +} + +/** + * Result of computing column profile + */ +export interface ProfileResult { + /** + * Number of null values in column + */ + null_count: number; + + /** + * Minimum value as string computed as part of histogram + */ + min_value: string; + + /** + * Maximum value as string computed as part of histogram + */ + max_value: string; + + /** + * Average value as string computed as part of histogram + */ + mean_value: string; + + /** + * Absolute count of values in each histogram bin + */ + histogram_bin_sizes: Array; + + /** + * Absolute floating-point width of a histogram bin + */ + histogram_bin_width: number; + + /** + * Quantile values computed from histogram bins + */ + histogram_quantiles: Array; + + /** + * Counts of distinct values in column + */ + freqtable_counts: Array; + + /** + * Number of other values not accounted for in counts + */ + freqtable_other_count: number; + +} + +/** + * The current backend state + */ +export interface BackendState { + /** + * The set of currently applied filters + */ + filters: Array; + + /** + * The set of currently applied sorts + */ + sort_keys: Array; + +} + +/** + * Schema for a column in a table + */ +export interface ColumnSchema { + /** + * Name of column as UTF-8 string + */ + name: string; + + /** + * Canonical name of data type class + */ + type_name: string; + + /** + * Column annotation / description + */ + description: string; + + /** + * Schema of nested child types + */ + children: Array; + + /** + * Precision for decimal types + */ + precision: number; + + /** + * Scale for decimal types + */ + scale: number; + + /** + * Time zone for timestamp with time zone + */ + timezone: string; + + /** + * Size parameter for fixed-size types (list, binary) + */ + type_size: number; + +} + +/** + * Column values formatted as strings + */ +export type ColumnFormattedData = Array; + +/** + * Specifies a table row filter based on a column's values + */ +export interface ColumnFilter { + /** + * Unique identifier for this filter + */ + filter_id: string; + + /** + * Type of filter to apply + */ + filter_type: string; + + /** + * String representation of a binary comparison + */ + compare_op: string; + + /** + * A stringified column value for a comparison filter + */ + compare_value: string; + + /** + * Array of column values for a set membership filter + */ + set_member_values: Array; + + /** + * Filter by including only values passed (true) or excluding (false) + */ + set_member_inclusive: boolean; + + /** + * Type of search to perform + */ + search_type: string; + + /** + * String value/regex to search for in stringified data + */ + search_term: string; + + /** + * If true, do a case-sensitive search, otherwise case-insensitive + */ + search_case_sensitive: boolean; + +} + +/** + * An exact or approximate quantile value from a column + */ +export interface ColumnQuantileValue { + /** + * Quantile number (percentile). E.g. 1 for 1%, 50 for median + */ + q: number; + + /** + * Stringified quantile value + */ + value: string; + + /** + * Whether value is exact or approximate (computed from binned data or + * sketches) + */ + exact: boolean; + +} + +/** + * Specifies a column to sort by + */ +export interface ColumnSortKey { + /** + * Column name to sort by + */ + column: string; + + /** + * Sort order, ascending (true) or descending (false) + */ + ascending: boolean; + +} + +export class PositronDataToolComm extends PositronBaseComm { + constructor(instance: IRuntimeClientInstance) { + super(instance); + } + + /** + * Request schema + * + * Request full schema for a table-like object + * + * + * @returns The schema for a table-like object + */ + getSchema(): Promise { + return super.performRpc('get_schema', [], []); + } + + /** + * Get a rectangle of data values + * + * Request a rectangular subset of data with values formatted as strings + * + * @param rowStartIndex First row to fetch (inclusive) + * @param rowEndIndex Last row to fetch (inclusive). May be beyond end + * @param columnStartIndex First column to fetch (inclusive) + * @param columnEndIndex Last column to fetch (inclusive). May extend + * beyond end + * + * @returns Table values formatted as strings + */ + getDataValues(rowStartIndex: number, rowEndIndex: number, columnStartIndex: number, columnEndIndex: number): Promise { + return super.performRpc('get_data_values', ['row_start_index', 'row_end_index', 'column_start_index', 'column_end_index'], [rowStartIndex, rowEndIndex, columnStartIndex, columnEndIndex]); + } + + /** + * Set column filters + * + * Set or clear column filters on table, replacing any previous filters + * + * @param filters Zero or more filters to apply + * + * @returns The result of applying filters to a table + */ + setColumnFilters(filters: Array): Promise { + return super.performRpc('set_column_filters', ['filters'], [filters]); + } + + /** + * Set or clear sort-by-column(s) + * + * Set or clear the columns(s) to sort by, replacing any previous sort + * columns + * + * @param sortKeys Pass zero or more keys to sort by. Clears any existing + * keys + * + */ + setSortColumns(sortKeys: Array): Promise { + return super.performRpc('set_sort_columns', ['sort_keys'], [sortKeys]); + } + + /** + * Get a column profile + * + * Requests a statistical summary or data profile for a column + * + * @param profileId Unique identifier for the requested profile + * @param profileType The type of analytical column profile + * @param column Column name to compute profile for + * + * @returns Result of computing column profile + */ + getColumnProfile(profileId: string, profileType: string, column: string): Promise { + return super.performRpc('get_column_profile', ['profile_id', 'profile_type', 'column'], [profileId, profileType, column]); + } + + /** + * Get the state + * + * Request the current backend state (applied filters and sort columns) + * + * + * @returns The current backend state + */ + getState(): Promise { + return super.performRpc('get_state', [], []); + } + +} + From 22a4f938c2dd64143cf956a33a47aacc51cdb875 Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Tue, 19 Dec 2023 12:08:27 -0500 Subject: [PATCH 23/71] Refactoring in Console --- .../browser/interfaces/positronConsoleService.ts | 4 +++- .../positronConsole/browser/positronConsoleService.ts | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService.ts b/src/vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService.ts index 58d83b12f20..fdd9b8b1ca4 100644 --- a/src/vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService.ts +++ b/src/vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService.ts @@ -33,7 +33,9 @@ export const enum PositronConsoleState { * IPositronConsoleService interface. */ export interface IPositronConsoleService { - // Needed for service branding in dependency injector. + /** + * Needed for service branding in dependency injector. + */ readonly _serviceBrand: undefined; /** diff --git a/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts b/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts index c98fe0bf47e..53dffb4f25a 100644 --- a/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts +++ b/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts @@ -5,10 +5,15 @@ import { localize } from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { generateUuid } from 'vs/base/common/uuid'; +import { PixelRatio } from 'vs/base/browser/browser'; import { IEditor } from 'vs/editor/common/editorCommon'; import { ILogService } from 'vs/platform/log/common/log'; import { IViewsService } from 'vs/workbench/common/views'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -35,11 +40,6 @@ import { ActivityItemInput, ActivityItemInputState } from 'vs/workbench/services import { ActivityItemErrorStream, ActivityItemOutputStream } from 'vs/workbench/services/positronConsole/browser/classes/activityItemStream'; import { IPositronConsoleInstance, IPositronConsoleService, POSITRON_CONSOLE_VIEW_ID, PositronConsoleState } from 'vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService'; import { formatLanguageRuntime, ILanguageRuntime, ILanguageRuntimeExit, ILanguageRuntimeMessage, ILanguageRuntimeService, RuntimeCodeExecutionMode, RuntimeCodeFragmentStatus, RuntimeErrorBehavior, RuntimeExitReason, RuntimeOnlineState, RuntimeState } from 'vs/workbench/services/languageRuntime/common/languageRuntimeService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements'; -import { PixelRatio } from 'vs/base/browser/browser'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; /** * The onDidChangeRuntimeItems throttle threshold and throttle interval. The throttle threshold From d06c67d8aac6c868a3610efebfae3b915d7f8e18 Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Tue, 19 Dec 2023 15:27:21 -0500 Subject: [PATCH 24/71] Adding PositronDataToolService --- .../actionBarComponents/layoutMenuButton.tsx | 12 +- .../browser/components/dataToolPanel.tsx | 28 +++- .../browser/positronDataTool.tsx | 6 +- .../browser/positronDataToolActions.ts | 12 +- .../browser/positronDataToolContext.tsx | 4 +- .../browser/positronDataToolEditor.tsx | 143 ++++++++++++++---- .../browser/positronDataToolState.tsx | 32 ++-- .../interfaces/positronDataToolService.ts | 60 ++++++++ .../browser/positronDataToolService.ts | 134 ++++++++++++++++ .../common/positronDataToolUri.ts | 50 ++++++ src/vs/workbench/workbench.common.main.ts | 3 +- 11 files changed, 411 insertions(+), 73 deletions(-) create mode 100644 src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts create mode 100644 src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts create mode 100644 src/vs/workbench/services/positronDataTool/common/positronDataToolUri.ts diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx index 417a97e5a23..6ad6008182c 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx @@ -7,8 +7,8 @@ import * as React from 'react'; import { localize } from 'vs/nls'; import { IAction, Separator } from 'vs/base/common/actions'; import { ActionBarMenuButton } from 'vs/platform/positronActionBar/browser/components/actionBarMenuButton'; -import { PositronDataToolLayout } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolState'; import { usePositronDataToolContext } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolContext'; +import { PositronDataToolLayout } from 'vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService'; /** * Localized strings. @@ -30,7 +30,7 @@ export const LayoutMenuButton = () => { // Builds the actions. const actions = () => { // Get the current layout. - const layout = positronDataToolContext.layout; + const layout = positronDataToolContext.positronDataToolInstance.layout; // Build the actions. const actions: IAction[] = []; @@ -44,7 +44,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsLeft, run: () => { - positronDataToolContext.setLayout(PositronDataToolLayout.ColumnsLeft); + positronDataToolContext.positronDataToolInstance.layout = PositronDataToolLayout.ColumnsLeft; } }); @@ -57,7 +57,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsRight, run: () => { - positronDataToolContext.setLayout(PositronDataToolLayout.ColumnsRight); + positronDataToolContext.positronDataToolInstance.layout = PositronDataToolLayout.ColumnsRight; } }); @@ -73,7 +73,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsHidden, run: () => { - positronDataToolContext.setLayout(PositronDataToolLayout.ColumnsHidden); + positronDataToolContext.positronDataToolInstance.layout = PositronDataToolLayout.ColumnsHidden; } }); @@ -86,7 +86,7 @@ export const LayoutMenuButton = () => { * @returns The icon ID for the layout. */ const selectIconId = () => { - switch (positronDataToolContext.layout) { + switch (positronDataToolContext.positronDataToolInstance.layout) { // Columns left. case PositronDataToolLayout.ColumnsLeft: return 'positron-data-tool-columns-left'; diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx index 089db37c3d8..5cb8fa15cf5 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx @@ -5,12 +5,13 @@ import 'vs/css!./dataToolPanel'; import * as React from 'react'; import { useEffect, useRef, useState } from 'react'; // eslint-disable-line no-duplicate-imports +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IReactComponentContainer } from 'vs/base/browser/positronReactRenderer'; import { PositronDataToolProps } from 'vs/workbench/contrib/positronDataTool/browser/positronDataTool'; -import { PositronDataToolLayout } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolState'; import { RowsPanel } from 'vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel'; import { usePositronDataToolContext } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolContext'; import { ColumnsPanel } from 'vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel'; +import { PositronDataToolLayout } from 'vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService'; import { PositronColumnSplitter, PositronColumnSplitterResizeResult } from 'vs/base/browser/ui/positronComponents/positronColumnSplitter'; /** @@ -43,11 +44,26 @@ export const DataToolPanel = (props: DataToolPanelProps) => { const column2 = useRef(undefined!); // State hooks. + const [layout, setLayout] = useState(positronDataToolContext.positronDataToolInstance.layout); const [columnWidth, setColumnWidth] = useState(200); + // Main useEffect. + useEffect(() => { + // Create the disposable store for cleanup. + const disposableStore = new DisposableStore(); + + // Add the onDidChangeLayout event handler. + disposableStore.add(positronDataToolContext.positronDataToolInstance.onDidChangeLayout(layout => { + setLayout(layout); + })); + + // Return the cleanup function that will dispose of the event handlers. + return () => disposableStore.dispose(); + }, []); + // Layout effect. useEffect(() => { - switch (positronDataToolContext.layout) { + switch (layout) { // Columns left. case PositronDataToolLayout.ColumnsLeft: dataToolPanel.current.style.gridTemplateRows = '[main] 1fr [end]'; @@ -102,7 +118,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { column2.current.style.display = 'inline'; break; } - }, [positronDataToolContext.layout, columnWidth]); + }, [layout, columnWidth]); // Width effect. useEffect(() => { @@ -115,8 +131,8 @@ export const DataToolPanel = (props: DataToolPanelProps) => { */ const resizeHandler = (x: number) => { // Calculate the new column width. - let newColumnWidth: number; - switch (positronDataToolContext.layout) { + let newColumnWidth = -1; + switch (layout) { // Columns left. case PositronDataToolLayout.ColumnsLeft: newColumnWidth = columnWidth + x; @@ -129,7 +145,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // Columns hidden. This can't happen. case PositronDataToolLayout.ColumnsHidden: - throw new Error('Sizer should not be available.'); + return PositronColumnSplitterResizeResult.TooLarge; } // If the new column width is too small, pin it at the minimum column width and return diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataTool.tsx b/src/vs/workbench/contrib/positronDataTool/browser/positronDataTool.tsx index 1e99b382419..2af405140d3 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataTool.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataTool.tsx @@ -9,13 +9,13 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IReactComponentContainer } from 'vs/base/browser/positronReactRenderer'; import { ActionBar } from 'vs/workbench/contrib/positronDataTool/browser/components/actionBar'; import { DataToolPanel } from 'vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel'; -import { PositronDataToolServices } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolState'; +import { PositronDataToolConfiguration } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolState'; import { PositronDataToolContextProvider } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolContext'; /** * PositronDataToolProps interface. */ -export interface PositronDataToolProps extends PositronDataToolServices { +export interface PositronDataToolProps extends PositronDataToolConfiguration { readonly reactComponentContainer: IReactComponentContainer; } @@ -29,7 +29,7 @@ export const PositronDataTool = (props: PropsWithChildren const [width, setWidth] = useState(props.reactComponentContainer.width); const [height, setHeight] = useState(props.reactComponentContainer.height); - // Add IReactComponentContainer event handlers. + // Main useEffect. useEffect(() => { // Create the disposable store for cleanup. const disposableStore = new DisposableStore(); diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolActions.ts b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolActions.ts index fdfe27b8c73..f3688da8069 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolActions.ts +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolActions.ts @@ -3,16 +3,13 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; -import { generateUuid } from 'vs/base/common/uuid'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IPositronDataToolService } from 'vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService'; /** * Positron data tool action category. @@ -70,9 +67,10 @@ class OpenPositronDataTool extends Action2 { */ async run(accessor: ServicesAccessor) { // Access services. - const editorService = accessor.get(IEditorService); - const resource = URI.from({ scheme: Schemas.positronDataTool, path: `data-tool-${generateUuid()}` }); - await editorService.openEditor({ resource }); + const positronDataToolService = accessor.get(IPositronDataToolService); + + // Test opening a positron data tool. + await positronDataToolService.testOpen('b809490e-1801-4f2f-911c-7b9539c78204'); } } diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolContext.tsx b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolContext.tsx index 611063954b3..28cbfc9cfb2 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolContext.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolContext.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { PropsWithChildren, createContext, useContext } from 'react'; // eslint-disable-line no-duplicate-imports -import { PositronDataToolServices, PositronDataToolState, usePositronDataToolState } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolState'; +import { PositronDataToolConfiguration, PositronDataToolState, usePositronDataToolState } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolState'; /** * Create the Positron data tool context. @@ -15,7 +15,7 @@ const PositronDataToolContext = createContext(undefined!) * Export the PositronDataToolContextProvider. */ export const PositronDataToolContextProvider = ( - props: PropsWithChildren + props: PropsWithChildren ) => { // State hooks. const positronDataToolState = usePositronDataToolState(props); diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx index 50bd1efc995..310946ac499 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx @@ -6,6 +6,8 @@ import 'vs/css!./positronDataToolEditor'; import * as React from 'react'; import * as DOM from 'vs/base/browser/dom'; import { Event, Emitter } from 'vs/base/common/event'; +import { IEditorOpenContext } from 'vs/workbench/common/editor'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -19,8 +21,10 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { PositronDataTool } from 'vs/workbench/contrib/positronDataTool/browser/positronDataTool'; +import { PositronDataToolUri } from 'vs/workbench/services/positronDataTool/common/positronDataToolUri'; import { IReactComponentContainer, ISize, PositronReactRenderer } from 'vs/base/browser/positronReactRenderer'; import { PositronDataToolEditorInput } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolEditorInput'; +import { IPositronDataToolService } from 'vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService'; // Temporary instance counter. let instance = 0; @@ -160,21 +164,27 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen //#endregion IReactComponentContainer //#region Constructor & Dispose - /** * Constructor. - * @param clipboardService The clipboard service. + * @param _clipboardService The clipboard service. + * @param _commandService The command service. + * @param _configurationService The configuration service. + * @param _contextKeyService The context key service. + * @param _contextMenuService The context menu service. + * @param _keybindingService The keybinding service. + * @param _positronDataToolService The Positron data tool service. * @param storageService The storage service. * @param telemetryService The telemetry service. * @param themeService The theme service. */ constructor( - @IClipboardService readonly clipboardService: IClipboardService, - @ICommandService private readonly commandService: ICommandService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IKeybindingService private readonly keybindingService: IKeybindingService, + @IClipboardService readonly _clipboardService: IClipboardService, + @ICommandService private readonly _commandService: ICommandService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IPositronDataToolService private readonly _positronDataToolService: IPositronDataToolService, @IStorageService storageService: IStorageService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @@ -183,18 +193,21 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen super(PositronDataToolEditorInput.EditorID, telemetryService, themeService, storageService); // Logging. - console.log(`PositronDataEditor ${this.instance} constructor`); + console.log(`PositronDataEditor ${this.instance} created.`); } /** * dispose override method. */ public override dispose(): void { - // Call the base class's dispose method. - super.dispose(); - // Logging. console.log(`PositronDataEditor ${this.instance} dispose`); + + // Dispose the PositronReactRenderer for the PositronDataTool. + this.disposePositronReactRenderer(); + + // Call the base class's dispose method. + super.dispose(); } //#endregion Constructor & Dispose @@ -212,21 +225,70 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen // Create and append the Positron data tool container. this._positronDataToolsContainer = DOM.$('.positron-data-tool-container'); parent.appendChild(this._positronDataToolsContainer); + } + + /** + * Sets the editor input. + * @param input The Positron data tool editor input. + * @param options The Positron data tool editor options. + * @param context The editor open context. + * @param token The cancellation token. + */ + override async setInput( + input: PositronDataToolEditorInput, + options: IPositronDataToolEditorOptions, + context: IEditorOpenContext, + token: CancellationToken + ): Promise { + // Call the base class's method. + await super.setInput(input, options, context, token); + + // Parse the Positron data tool URI. + const identifier = PositronDataToolUri.parse(input.resource); + if (identifier) { + // Get the Positron data tool instance. + const positronDataToolInstance = this._positronDataToolService.getInstance(identifier); + + // If the Positron data tool instance was found, render the Positron data tool. + if (positronDataToolInstance) { + // Create the PositronReactRenderer for the PositronDataTool component and render it. + this._positronReactRenderer = new PositronReactRenderer(this._positronDataToolsContainer); + this._positronReactRenderer.render( + + ); + + // Success. + return; + } + } + + // TODO: Render some kind of an error. + + // Logging. + console.log(`PositronDataEditor ${this.instance} setInput ${input.resource}`); + } + + /** + * Clears the input. + */ + override clearInput(): void { + // Logging. + console.log(`PositronDataEditor ${this.instance} clearInput`); + + // Dispose the PositronReactRenderer for the PositronDataTool. + this.disposePositronReactRenderer(); - // Create the PositronReactRenderer for the PositronDataTool component and render it. - this._positronReactRenderer = new PositronReactRenderer(this._positronDataToolsContainer); - this._register(this._positronReactRenderer); - this._positronReactRenderer.render( - - ); + // Call the base class's method. + super.clearInput(); } /** @@ -235,11 +297,11 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen * @param group The editor group. */ protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - // Call the base class's method. - super.setEditorVisible(visible, group); - // Logging. console.log(`PositronDataEditor ${this.instance} setEditorVisible ${visible} group ${group}`); + + // Call the base class's method. + super.setEditorVisible(visible, group); } //#endregion Protected Overrides @@ -251,6 +313,9 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen * @param dimension The layout dimension. */ override layout(dimension: DOM.Dimension): void { + // Logging. + console.log(`PositronDataEditor ${this.instance} layout ${dimension.width},${dimension.height}`); + // Size the container. DOM.size(this._positronDataToolsContainer, dimension.width, dimension.height); @@ -264,4 +329,24 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen } //#endregion Protected Overrides + + //#region Private Methods + + /** + * Disposes of the PositronReactRenderer for the PositronDataTool. + */ + private disposePositronReactRenderer() { + // If the PositronReactRenderer for the PositronDataTool is exists, dispose it. This removes + // the PositronDataTool from the DOM. + if (this._positronReactRenderer) { + // Logging. + console.log(`PositronDataEditor ${this.instance} disposePositronReactRenderer`); + + // Dispose of the PositronReactRenderer for the PositronDataTool. + this._positronReactRenderer.dispose(); + this._positronReactRenderer = undefined; + } + } + + //#endregion Private Methods } diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolState.tsx b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolState.tsx index 1172586d481..de3ec81cc2a 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolState.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolState.tsx @@ -2,43 +2,39 @@ * Copyright (C) 2023 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { useEffect, useState } from 'react'; // eslint-disable-line no-duplicate-imports +import { useEffect } from 'react'; // eslint-disable-line no-duplicate-imports import { DisposableStore } from 'vs/base/common/lifecycle'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { PositronActionBarServices } from 'vs/platform/positronActionBar/browser/positronActionBarState'; +import { IPositronDataToolInstance } from 'vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService'; /** - * PositronDataToolLayout enumeration. + * PositronDataToolServices interface. */ -export enum PositronDataToolLayout { - ColumnsLeft = 'ColumnsLeft', - ColumnsRight = 'ColumnsRight', - ColumnsHidden = 'ColumnsHidden', +export interface PositronDataToolServices extends PositronActionBarServices { + readonly clipboardService: IClipboardService; } /** - * PositronDataToolServices interface. + * PositronDataToolConfiguration interface. */ -export interface PositronDataToolServices extends PositronActionBarServices { - readonly clipboardService: IClipboardService; +export interface PositronDataToolConfiguration extends PositronDataToolServices { + readonly positronDataToolInstance: IPositronDataToolInstance; } /** * PositronDataToolState interface. */ -export interface PositronDataToolState extends PositronDataToolServices { - layout: PositronDataToolLayout; - setLayout(layout: PositronDataToolLayout): void; +export interface PositronDataToolState extends PositronDataToolConfiguration { } /** * The usePositronDataToolState custom hook. * @returns The hook. */ -export const usePositronDataToolState = (services: PositronDataToolServices): PositronDataToolState => { - // Hooks. - const [layout, setLayout] = useState(PositronDataToolLayout.ColumnsLeft); - +export const usePositronDataToolState = ( + configuration: PositronDataToolConfiguration +): PositronDataToolState => { // Add event handlers. useEffect(() => { // Create a disposable store for the event handlers we'll add. @@ -50,8 +46,6 @@ export const usePositronDataToolState = (services: PositronDataToolServices): Po // Return the Positron data tool state. return { - ...services, - layout, - setLayout + ...configuration, }; }; diff --git a/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts new file mode 100644 index 00000000000..02f3fcbc513 --- /dev/null +++ b/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +// Create the decorator for the Positron data tool service (used in dependency injection). +export const IPositronDataToolService = createDecorator('positronDataToolService'); + +/** + * PositronDataToolLayout enumeration. + */ +export enum PositronDataToolLayout { + ColumnsLeft = 'ColumnsLeft', + ColumnsRight = 'ColumnsRight', + ColumnsHidden = 'ColumnsHidden', +} + +/** + * IPositronDataToolService interface. + */ +export interface IPositronDataToolService { + /** + * Needed for service branding in dependency injector. + */ + readonly _serviceBrand: undefined; + + /** + * Test open function. + * @param identifier The identifier. + */ + testOpen(identifier: string): Promise; + + /** + * + * @param identifier + */ + getInstance(identifier: string): IPositronDataToolInstance | undefined; +} + +/** + * IPositronDataToolInstance interface. + */ +export interface IPositronDataToolInstance { + /** + * Gets the identifier. + */ + readonly identifier: string; + + /** + * Gets or sets the layout. + */ + layout: PositronDataToolLayout; + + /** + * The onDidChangeLayout event. + */ + readonly onDidChangeLayout: Event; +} diff --git a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts new file mode 100644 index 00000000000..682a9cc4ae2 --- /dev/null +++ b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { PositronDataToolUri } from 'vs/workbench/services/positronDataTool/common/positronDataToolUri'; +import { IPositronDataToolInstance, IPositronDataToolService, PositronDataToolLayout } from 'vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService'; + +/** + * PositronDataToolService class. + */ +class PositronDataToolService extends Disposable implements IPositronDataToolService { + //#region Private Properties + + /** + * The Positron data tool instance map. + */ + private _positronDataToolInstanceMap = new Map(); + + //#endregion Private Properties + + //#region Constructor & Dispose + + /** + * Constructor. + * @param _editorService The editor service. + */ + constructor( + @IEditorService private readonly _editorService: IEditorService + ) { + // Call the disposable constrcutor. + super(); + + this._positronDataToolInstanceMap.entries(); + } + + //#endregion Constructor & Dispose + + //#region IPositronDataToolService Implementation + + /** + * Needed for service branding in dependency injector. + */ + declare readonly _serviceBrand: undefined; + + /** + * Test open function. + */ + async testOpen(identifier: string): Promise { + if (!this._positronDataToolInstanceMap.has(identifier)) { + const positronDataToolInstance = new PositronDataToolInstance(identifier); + this._positronDataToolInstanceMap.set(identifier, positronDataToolInstance); + } + + await this._editorService.openEditor({ + resource: PositronDataToolUri.generate(identifier) + }); + } + + /** + * + * @param identifier + */ + getInstance(identifier: string): IPositronDataToolInstance | undefined { + return this._positronDataToolInstanceMap.get(identifier); + } + + //#endregion IPositronDataToolService Implementation +} + +/** +* PositronDataToolInstance class. +*/ +class PositronDataToolInstance extends Disposable implements IPositronDataToolInstance { + //#region Private Properties + + /** + * Gets or sets the layout. + */ + private _layout = PositronDataToolLayout.ColumnsLeft; + + /** + * The onDidChangeLayout event emitter. + */ + private readonly _onDidChangeLayoutEmitter = + this._register(new Emitter); + + //#endregion Private Properties + + //#region Constructor & Dispose + + /** + * Constructor. + * @param identifier The identifier. + */ + constructor(readonly identifier: string) { + // Call the base class's constructor. + super(); + } + + //#endregion Constructor & Dispose + + //#region IPositronDataToolInstance Implementation + + /** + * Gets the layout. + */ + get layout() { + return this._layout; + } + + /** + * Sets the layout. + */ + set layout(layout: PositronDataToolLayout) { + if (layout !== this._layout) { + this._layout = layout; + this._onDidChangeLayoutEmitter.fire(this._layout); + } + } + + /** + * onDidChangeLayout event. + */ + readonly onDidChangeLayout = this._onDidChangeLayoutEmitter.event; + + //#endregion IPositronDataToolInstance Implementation +} + +// Register the Positron data tool service. +registerSingleton(IPositronDataToolService, PositronDataToolService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/positronDataTool/common/positronDataToolUri.ts b/src/vs/workbench/services/positronDataTool/common/positronDataToolUri.ts new file mode 100644 index 00000000000..6e8161def9e --- /dev/null +++ b/src/vs/workbench/services/positronDataTool/common/positronDataToolUri.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; + +/** + * PositronDataToolUri class. + */ +export class PositronDataToolUri { + /** + * The Positron data tool URI scheme. + */ + public static Scheme = Schemas.positronDataTool; + + /** + * Generates a Positron data tool URI. + * @param identifier The identifier. + * @returns The Positron data tool URI. + */ + public static generate(identifier: string): URI { + return URI.from({ + scheme: PositronDataToolUri.Scheme, + path: `positron-data-tool-${identifier}` + }); + } + + /** + * Parses a Positron data tool URI. + * @param resource The resource. + * @returns The identifier, if successful; otherwise, undefined. + */ + public static parse(resource: URI): string | undefined { + // Check the scheme. + if (resource.scheme !== PositronDataToolUri.Scheme) { + return undefined; + } + + // Parse the resource. + const match = resource.path.match(/^positron-data-tool-([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})$/); + const identifier = match?.[1]; + if (typeof identifier !== 'string') { + return undefined; + } + + // Return the identifier. + return identifier; + } +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 747822acbed..e07e9204ba6 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -425,8 +425,9 @@ import 'vs/workbench/contrib/positronIPyWidgets/browser/positronIPyWidgets.contr // Workbench services import 'vs/workbench/services/languageRuntime/common/languageRuntime'; -import 'vs/workbench/contrib/positronHelp/browser/positronHelpService'; import 'vs/workbench/services/positronConsole/browser/positronConsoleService'; +import 'vs/workbench/contrib/positronHelp/browser/positronHelpService'; import 'vs/workbench/services/positronVariables/common/positronVariablesService'; +import 'vs/workbench/services/positronDataTool/browser/positronDataToolService'; // --- End Positron --- From d12f857a76ac0537c87a7c5e372ae19f6c8fc07e Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Tue, 19 Dec 2023 16:37:10 -0500 Subject: [PATCH 25/71] Maintain columns width percent between instances --- .../browser/components/dataToolPanel.tsx | 28 +++++++++------- .../interfaces/positronDataToolService.ts | 10 ++++++ .../browser/positronDataToolService.ts | 32 +++++++++++++++++++ 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx index 5cb8fa15cf5..329db238ae0 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx @@ -45,7 +45,12 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // State hooks. const [layout, setLayout] = useState(positronDataToolContext.positronDataToolInstance.layout); - const [columnWidth, setColumnWidth] = useState(200); + const [columnsWidth, setColumnsWidth] = useState( + Math.max( + positronDataToolContext.positronDataToolInstance.columnsWidthPercent * props.width, + MIN_COLUMN_WIDTH + ) + ); // Main useEffect. useEffect(() => { @@ -67,7 +72,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // Columns left. case PositronDataToolLayout.ColumnsLeft: dataToolPanel.current.style.gridTemplateRows = '[main] 1fr [end]'; - dataToolPanel.current.style.gridTemplateColumns = `[column-1] ${columnWidth}px [splitter] 8px [column-2] 1fr [end]`; + dataToolPanel.current.style.gridTemplateColumns = `[column-1] ${columnsWidth}px [splitter] 8px [column-2] 1fr [end]`; column1.current.style.gridRow = 'main / end'; column1.current.style.gridColumn = 'column-1 / splitter'; @@ -85,7 +90,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // Columns right. case PositronDataToolLayout.ColumnsRight: dataToolPanel.current.style.gridTemplateRows = '[main] 1fr [end]'; - dataToolPanel.current.style.gridTemplateColumns = `[column-1] 1fr [splitter] 8px [column-2] ${columnWidth}px [end]`; + dataToolPanel.current.style.gridTemplateColumns = `[column-1] 1fr [splitter] 8px [column-2] ${columnsWidth}px [end]`; column1.current.style.gridRow = 'main / end'; column1.current.style.gridColumn = 'column-2 / end'; @@ -118,7 +123,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { column2.current.style.display = 'inline'; break; } - }, [layout, columnWidth]); + }, [layout, columnsWidth]); // Width effect. useEffect(() => { @@ -135,12 +140,12 @@ export const DataToolPanel = (props: DataToolPanelProps) => { switch (layout) { // Columns left. case PositronDataToolLayout.ColumnsLeft: - newColumnWidth = columnWidth + x; + newColumnWidth = columnsWidth + x; break; // Columns right. case PositronDataToolLayout.ColumnsRight: - newColumnWidth = columnWidth - x; + newColumnWidth = columnsWidth - x; break; // Columns hidden. This can't happen. @@ -151,7 +156,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // If the new column width is too small, pin it at the minimum column width and return // ColumnSplitterResizeResult.TooSmall to get the cursor updated. if (newColumnWidth < MIN_COLUMN_WIDTH) { - setColumnWidth(MIN_COLUMN_WIDTH); + setColumnsWidth(MIN_COLUMN_WIDTH); return PositronColumnSplitterResizeResult.TooSmall; } @@ -159,13 +164,14 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // ColumnSplitterResizeResult.TooLarge to get the cursor updated. const maxColumnWidth = props.width - (MIN_COLUMN_WIDTH + 24); if (newColumnWidth > maxColumnWidth) { - setColumnWidth(maxColumnWidth); + setColumnsWidth(maxColumnWidth); return PositronColumnSplitterResizeResult.TooLarge; } - // Set the column width and return ColumnSplitterResizeResult.Resizing to get the cursor - // updated. - setColumnWidth(newColumnWidth); + // Update the the column width and return ColumnSplitterResizeResult.Resizing to get the + // cursor updated. + setColumnsWidth(newColumnWidth); + positronDataToolContext.positronDataToolInstance.columnsWidthPercent = newColumnWidth / props.width; return PositronColumnSplitterResizeResult.Resizing; }; diff --git a/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts index 02f3fcbc513..25ca795f6e8 100644 --- a/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts +++ b/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts @@ -53,8 +53,18 @@ export interface IPositronDataToolInstance { */ layout: PositronDataToolLayout; + /** + * Gets or sets the columns width percent. + */ + columnsWidthPercent: number; + /** * The onDidChangeLayout event. */ readonly onDidChangeLayout: Event; + + /** + * The onDidChangeColumnsWidthPercent event. + */ + readonly onDidChangeColumnsWidthPercent: Event; } diff --git a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts index 682a9cc4ae2..f42600e01e5 100644 --- a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts +++ b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts @@ -82,12 +82,22 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn */ private _layout = PositronDataToolLayout.ColumnsLeft; + /** + * Gets or sets the columns width percent. + */ + private _columnsWidthPercent = 0.25; + /** * The onDidChangeLayout event emitter. */ private readonly _onDidChangeLayoutEmitter = this._register(new Emitter); + /** + * The onDidChangeColumnsWidthPercent event emitter. + */ + private readonly _onDidChangeColumnsWidthPercentEmitter = this._register(new Emitter); + //#endregion Private Properties //#region Constructor & Dispose @@ -122,11 +132,33 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn } } + /** + * Gets the columns width. + */ + get columnsWidthPercent() { + return this._columnsWidthPercent; + } + + /** + * Sets the columns width. + */ + set columnsWidthPercent(columnsWidthPercent: number) { + if (columnsWidthPercent !== this._columnsWidthPercent) { + this._columnsWidthPercent = columnsWidthPercent; + this._onDidChangeColumnsWidthPercentEmitter.fire(this._columnsWidthPercent); + } + } + /** * onDidChangeLayout event. */ readonly onDidChangeLayout = this._onDidChangeLayoutEmitter.event; + /** + * onDidChangeColumnsWidthPercent event. + */ + readonly onDidChangeColumnsWidthPercent = this._onDidChangeColumnsWidthPercentEmitter.event; + //#endregion IPositronDataToolInstance Implementation } From 2c8123c5c7adc0980da8cf701d57528abb2154b8 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Tue, 19 Dec 2023 14:08:54 -0800 Subject: [PATCH 26/71] implement enum visitor --- positron/comms/generate-comms.ts | 61 ++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 5e16398e57b..5e0fc32b57a 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -162,6 +162,28 @@ function formatLines(line: string): string[] { return lines; } +function* enumVisitor( + key: string, + contract: any, + callback: (key: string, e: Array) => Generator +): Generator { + if (contract.enum) { + yield* callback(key, contract.enum); + } else if (typeof contract === 'object') { + for (const key of Object.keys(contract)) { + if (key === 'schema' && contract['name']) { + yield* enumVisitor(contract['name'], contract[key], callback); + } else { + yield* enumVisitor(key, contract[key], callback); + } + } + } else if (Array.isArray(contract)) { + for (const item of contract) { + yield* enumVisitor(key, item, callback); + } + } +} + /** * Formats a comment, breaking it into multiple lines and adding a leader to * each line. @@ -246,31 +268,24 @@ use serde::Serialize; if (!source) { continue; } - for (const method of source.methods) { - for (const param of method.params) { - if (param.schema.enum) { - yield formatComment(`/// `, - `Possible values for the ` + - snakeCaseToSentenceCase(param.name) + ` ` + - `parameter of the ` + - snakeCaseToSentenceCase(method.name) + ` ` + - `method.`); - yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; - yield `pub enum ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)} {\n`; - for (let i = 0; i < param.schema.enum.length; i++) { - const value = param.schema.enum[i]; - yield `\t#[serde(rename = "${value}")]\n`; - yield `\t${snakeCaseToSentenceCase(value)}`; - if (i < param.schema.enum.length - 1) { - yield ',\n\n'; - } else { - yield '\n'; - } - } - yield '}\n\n'; + yield* enumVisitor('', source, function* (key: string, values: Array) { + yield formatComment(`/// `, + `Possible values for ` + + snakeCaseToSentenceCase(key)); + yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; + yield `pub enum ${snakeCaseToSentenceCase(key)} {\n`; + for (let i = 0; i < values.length; i++) { + const value = values[i]; + yield `\t#[serde(rename = "${value}")]\n`; + yield `\t${snakeCaseToSentenceCase(value)}`; + if (i < values.length - 1) { + yield ',\n\n'; + } else { + yield '\n'; } } - } + yield '}\n\n'; + }); } for (const source of [backend, frontend]) { From 1b77fcb7f5d0f92d8fb6864fc332d61c1bde3f23 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Tue, 19 Dec 2023 14:24:45 -0800 Subject: [PATCH 27/71] keep context in enum visitor --- positron/comms/generate-comms.ts | 33 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 5e0fc32b57a..f096616ed8b 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -163,23 +163,29 @@ function formatLines(line: string): string[] { } function* enumVisitor( - key: string, + context: Array, contract: any, - callback: (key: string, e: Array) => Generator + callback: (context: Array, e: Array) => Generator ): Generator { if (contract.enum) { - yield* callback(key, contract.enum); + yield* callback(context, contract.enum); + } else if (Array.isArray(contract)) { + for (const item of contract) { + yield* enumVisitor(context, item, callback); + } } else if (typeof contract === 'object') { for (const key of Object.keys(contract)) { - if (key === 'schema' && contract['name']) { - yield* enumVisitor(contract['name'], contract[key], callback); + if (contract['name']) { + yield* enumVisitor( + [contract['name'], ...context], contract[key], callback); + } else if (key === 'properties' || key === 'params') { + yield* enumVisitor( + context, contract[key], callback); } else { - yield* enumVisitor(key, contract[key], callback); + yield* enumVisitor( + [key, ...context], contract[key], callback); } - } - } else if (Array.isArray(contract)) { - for (const item of contract) { - yield* enumVisitor(key, item, callback); + } } } @@ -268,12 +274,13 @@ use serde::Serialize; if (!source) { continue; } - yield* enumVisitor('', source, function* (key: string, values: Array) { + yield* enumVisitor([], source, function* (context: Array, values: Array) { yield formatComment(`/// `, `Possible values for ` + - snakeCaseToSentenceCase(key)); + snakeCaseToSentenceCase(context[0]) + ` in ` + + snakeCaseToSentenceCase(context[1])); yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; - yield `pub enum ${snakeCaseToSentenceCase(key)} {\n`; + yield `pub enum ${snakeCaseToSentenceCase(context[1])}${snakeCaseToSentenceCase(context[0])} {\n`; for (let i = 0; i < values.length; i++) { const value = values[i]; yield `\t#[serde(rename = "${value}")]\n`; From 3fffb5528838c0b8d17f9a48ecb96e0329ee0835 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Tue, 19 Dec 2023 14:33:13 -0800 Subject: [PATCH 28/71] emit typescript enum types using visitor --- positron/comms/generate-comms.ts | 28 ++++++++++++- .../common/positronDataToolComm.ts | 41 +++++++++++++++++++ .../common/positronHelpComm.ts | 11 ++++- .../common/positronVariablesComm.ts | 16 ++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index f096616ed8b..df24e3a4f89 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -674,6 +674,32 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co } } + // Create enums for all enum types + for (const source of [backend, frontend]) { + if (!source) { + continue; + } + yield* enumVisitor([], source, function* (context: Array, values: Array) { + yield '/**\n'; + yield formatComment(` * `, + `Possible values for ` + + snakeCaseToSentenceCase(context[0]) + ` in ` + + snakeCaseToSentenceCase(context[1])); + yield ' */\n'; + yield `enum ${snakeCaseToSentenceCase(context[1])}${snakeCaseToSentenceCase(context[0])} {\n`; + for (let i = 0; i < values.length; i++) { + const value = values[i]; + yield `\t${snakeCaseToSentenceCase(value)} = '${value}'`; + if (i < values.length - 1) { + yield ',\n'; + } else { + yield '\n'; + } + } + yield '}\n\n'; + }); + } + for (const source of [backend, frontend]) { if (!source) { continue; @@ -713,7 +739,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co yield '\t */\n'; yield `\t${snakeCaseToCamelCase(param.name)}: `; if (param.schema.type === 'string' && param.schema.enum) { - yield param.schema.enum.map((value: string) => `'${value}'`).join(' | '); + yield `${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { yield deriveType(frontend, TypescriptTypeMap, param.schema); } diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index 0645802bfb7..864ee717b49 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -120,6 +120,47 @@ export interface BackendState { } +/** + * Possible values for ProfileType in GetColumnProfile + */ +enum GetColumnProfileProfileType { + Freqtable = 'freqtable', + Histogram = 'histogram' +} + +/** + * Possible values for FilterType in ColumnFilter + */ +enum ColumnFilterFilterType { + Isnull = 'isnull', + Notnull = 'notnull', + Compare = 'compare', + SetMembership = 'set_membership', + Search = 'search' +} + +/** + * Possible values for CompareOp in ColumnFilter + */ +enum ColumnFilterCompareOp { + == = '==', + != = '!=', + < = '<', + <= = '<=', + > = '>', + >= = '>=' +} + +/** + * Possible values for SearchType in ColumnFilter + */ +enum ColumnFilterSearchType { + Contains = 'contains', + Startswith = 'startswith', + Endswith = 'endswith', + Regex = 'regex' +} + /** * Schema for a column in a table */ diff --git a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts index 79344a03c3b..02a69dfd84b 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts @@ -10,6 +10,15 @@ import { Event } from 'vs/base/common/event'; import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; +/** + * Possible values for Kind in ShowHelp + */ +enum ShowHelpKind { + Html = 'html', + Markdown = 'markdown', + Url = 'url' +} + /** * Event: Request to show help in the frontend */ @@ -22,7 +31,7 @@ export interface ShowHelpEvent { /** * The type of content to show */ - kind: 'html' | 'markdown' | 'url'; + kind: ShowHelpKind; /** * Whether to focus the Help pane when the content is displayed. diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index e81cfc5038a..58c5b753dd7 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -44,6 +44,22 @@ export interface FormattedVariable { } +/** + * Possible values for Kind in Variable + */ +enum VariableKind { + Boolean = 'boolean', + Bytes = 'bytes', + Collection = 'collection', + Empty = 'empty', + Function = 'function', + Map = 'map', + Number = 'number', + Other = 'other', + String = 'string', + Table = 'table' +} + /** * A single variable in the runtime. */ From 194d3821004bf9f6927e40905072ce27efc5fca8 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Tue, 19 Dec 2023 14:42:35 -0800 Subject: [PATCH 29/71] sanitize identifiers; use enum vals in TS interfaces --- positron/comms/generate-comms.ts | 6 ++++++ .../common/positronDataToolComm.ts | 18 +++++++++--------- .../common/positronVariablesComm.ts | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index df24e3a4f89..af8948a617b 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -84,6 +84,10 @@ const PythonTypeMap: Record = { * @returns A camelCase name */ function snakeCaseToCamelCase(name: string) { + name = name.replace(/=/g, 'Eq'); + name = name.replace(/!/g, 'Not'); + name = name.replace(//g, 'Gt'); return name.replace(/_([a-z])/g, (m) => m[1].toUpperCase()); } @@ -635,6 +639,8 @@ async function* createTypescriptInterface(contract: any, name: string, yield `\t${prop}: `; if (schema.type === 'object') { yield snakeCaseToSentenceCase(schema.name); + } else if (schema.type === 'string' && schema.enum) { + yield `${snakeCaseToSentenceCase(name)}${snakeCaseToSentenceCase(prop)}`; } else { yield deriveType(contract, TypescriptTypeMap, schema); } diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index 864ee717b49..34ab4df5dd6 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -143,12 +143,12 @@ enum ColumnFilterFilterType { * Possible values for CompareOp in ColumnFilter */ enum ColumnFilterCompareOp { - == = '==', - != = '!=', - < = '<', - <= = '<=', - > = '>', - >= = '>=' + EqEq = '==', + NotEq = '!=', + Lt = '<', + LtEq = '<=', + Gt = '>', + GtEq = '>=' } /** @@ -224,12 +224,12 @@ export interface ColumnFilter { /** * Type of filter to apply */ - filter_type: string; + filter_type: ColumnFilterFilterType; /** * String representation of a binary comparison */ - compare_op: string; + compare_op: ColumnFilterCompareOp; /** * A stringified column value for a comparison filter @@ -249,7 +249,7 @@ export interface ColumnFilter { /** * Type of search to perform */ - search_type: string; + search_type: ColumnFilterSearchType; /** * String value/regex to search for in stringified data diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index 58c5b753dd7..7ec54403a0d 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -95,7 +95,7 @@ export interface Variable { * The kind of value the variable represents, such as 'string' or * 'number' */ - kind: string; + kind: VariableKind; /** * The number of elements in the variable, if it is a collection From 987378785ba76d4c67426b380be4f91dc162610d Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Tue, 19 Dec 2023 15:16:43 -0800 Subject: [PATCH 30/71] use enum visitor to generate python enums --- positron/comms/generate-comms.ts | 45 ++++++++++++++------------------ 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index af8948a617b..a4622b37d11 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -470,34 +470,29 @@ from dataclasses import dataclass, field if (!source) { continue; } - for (const method of source.methods) { - for (const param of method.params) { - if (param.schema.enum) { - yield '@enum.unique\n'; - yield `class ${snakeCaseToSentenceCase(method.name)}`; - yield `${snakeCaseToSentenceCase(param.name)}(str, enum.Enum):\n`; - yield ' """\n'; - yield formatComment(` `, - `Possible values for the ` + - snakeCaseToSentenceCase(param.name) + ` ` + - `parameter of the ` + - snakeCaseToSentenceCase(method.name) + ` ` + - `method.`); - yield ' """\n'; - yield '\n'; - for (let i = 0; i < param.schema.enum.length; i++) { - const value = param.schema.enum[i]; - yield ` ${snakeCaseToSentenceCase(value)} = "${value}"`; - if (i < param.schema.enum.length - 1) { - yield '\n\n'; - } else { - yield '\n'; - } - } + yield* enumVisitor([], source, function* (context: Array, values: Array) { + yield '@enum.unique\n'; + yield `class ${snakeCaseToSentenceCase(context[1])}`; + yield `${snakeCaseToSentenceCase(context[0])}(str, enum.Enum):\n`; + yield ' """\n'; + yield formatComment(` `, + `Possible values for ` + + snakeCaseToSentenceCase(context[0]) + + ` in ` + + snakeCaseToSentenceCase(context[1])); + yield ' """\n'; + yield '\n'; + for (let i = 0; i < values.length; i++) { + const value = values[i]; + yield ` ${snakeCaseToSentenceCase(value)} = "${value}"`; + if (i < values.length - 1) { yield '\n\n'; + } else { + yield '\n'; } } - } + yield '\n\n'; + }); } if (backend) { From e02c7911c904b7013424057b5945da1d4d8f1830 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Tue, 19 Dec 2023 15:21:42 -0800 Subject: [PATCH 31/71] use typescript enums in parameter types --- positron/comms/generate-comms.ts | 10 +++++++--- .../languageRuntime/common/positronDataToolComm.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index a4622b37d11..332dcb806de 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -804,9 +804,13 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (!param.schema) { throw new Error(`No schema for '${method.name}' parameter '${param.name}'`); } - yield snakeCaseToCamelCase(param.name) + - ': ' + - deriveType(backend, TypescriptTypeMap, param.schema); + yield snakeCaseToCamelCase(param.name) + ': '; + const schema = param.schema; + if (schema.type === 'string' && schema.enum) { + yield `${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; + } else { + yield deriveType(backend, TypescriptTypeMap, schema); + } if (i < method.params.length - 1) { yield ', '; } diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index 34ab4df5dd6..ad0f97dc387 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -373,7 +373,7 @@ export class PositronDataToolComm extends PositronBaseComm { * * @returns Result of computing column profile */ - getColumnProfile(profileId: string, profileType: string, column: string): Promise { + getColumnProfile(profileId: string, profileType: GetColumnProfileProfileType, column: string): Promise { return super.performRpc('get_column_profile', ['profile_id', 'profile_type', 'column'], [profileId, profileType, column]); } From 25ade7058ec59b90b802ac45ce566b5622d46b87 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Tue, 19 Dec 2023 16:09:25 -0800 Subject: [PATCH 32/71] clean up imports and comments --- positron/comms/generate-comms.ts | 173 ++++++++++++++++++++++++++----- 1 file changed, 148 insertions(+), 25 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 332dcb806de..50227cc519a 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -11,9 +11,8 @@ */ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs'; -import { compile } from 'json-schema-to-typescript'; import { execSync } from 'child_process'; -import path, { format } from 'path'; +import path from 'path'; const commsDir = `${__dirname}`; const commsFiles = readdirSync(commsDir); @@ -78,7 +77,7 @@ const PythonTypeMap: Record = { }; /** - * Converter from snake_case to camelCase + * Converter from snake_case to camelCase. Also replaces some special characters * * @param name A snake_case name * @returns A camelCase name @@ -102,12 +101,15 @@ function snakeCaseToSentenceCase(name: string) { } /** - * Parse a ref tag + * Parse a ref tag to get the name of the object referred to by the ref. * * @param ref The ref to parse - * @returns The name of the object referred to by the ref + * @returns The name of the object referred to by the ref, as a SentenceCase + * string */ function parseRef(ref: string, contract: any): string { + // Split the ref into parts, and then walk the contract to find the + // referenced object const parts = ref.split('/'); let target = contract; for (let i = 0; i < parts.length; i++) { @@ -123,18 +125,32 @@ function parseRef(ref: string, contract: any): string { return snakeCaseToSentenceCase(parts[parts.length - 1]); } -function deriveType(contract: any, typeMap: Record, schema: any): string { +/** + * Generic function for deriving a type from a schema. + * + * @param contract The OpenRPC contract that the schema is part of + * @param typeMap A map from schema types to language types + * @param schema The schema to derive a type from + * + * @returns A string containing the derived type + */ +function deriveType(contract: any, + typeMap: Record, + schema: any): string { if (schema.type === 'array') { if (schema.items.$ref) { + // If the array has a ref, use that to derive an array type return typeMap['array-begin'] + parseRef(schema.items.$ref, contract) + typeMap['array-end']; } else { + // Otherwise use the type of the items directly return typeMap['array-begin'] + typeMap[schema.items.type] + typeMap['array-end']; } } else if (schema.type === 'object' && schema.$ref) { + // If the object has a ref, use that to derive an object type return parseRef(schema.$ref, contract); } else { if (Object.keys(typeMap).includes(schema.type)) { @@ -145,8 +161,13 @@ function deriveType(contract: any, typeMap: Record, schema: any) } } -// Breaks a single line of text into multiple lines, each of which is no longer than -// 70 characters. +/** + * Breaks a single line of text into multiple lines, each of which is no longer + * than 70 characters. + * + * @param line The line to break + * @returns An array of lines + */ function formatLines(line: string): string[] { const words = line.split(' '); const lines = new Array(); @@ -166,26 +187,64 @@ function formatLines(line: string): string[] { return lines; } +/** + * Formats a comment, breaking it into multiple lines and adding a leader to + * each line. + * + * @param leader The leader to use for each line + * @param comment The comment to format + * @returns The formatted comment + */ +function formatComment(leader: string, comment: string): string { + const lines = formatLines(comment); + let result = ''; + for (const line of lines) { + result += leader + line + '\n'; + } + return result; +} + +/** + * Visitor function for enums in an OpenRPC contract. Recursively discovers all + * enum values and calls the callback function for each enum. + * + * @param context The current context stack (names of objects leading to the enum) + * @param contract The OpenRPC contract to visit + * @param callback The callback function to call for each enum + * + * @returns An generator that yields the results of the callback function, + * invoked for each enum + */ function* enumVisitor( context: Array, contract: any, callback: (context: Array, e: Array) => Generator ): Generator { if (contract.enum) { + // If this object has an enum, call the callback function and yield the + // result yield* callback(context, contract.enum); } else if (Array.isArray(contract)) { + // If this object is an array, recurse into each item for (const item of contract) { yield* enumVisitor(context, item, callback); } } else if (typeof contract === 'object') { + // If this object is an object, recurse into each property for (const key of Object.keys(contract)) { if (contract['name']) { + // If this is a named object, push the name onto the context + // and recurse yield* enumVisitor( [contract['name'], ...context], contract[key], callback); } else if (key === 'properties' || key === 'params') { + // If this is a properties or params object, recurse into each + // property, but don't push the parent name onto the context yield* enumVisitor( context, contract[key], callback); } else { + // For all other objects, push the key onto the context and + // recurse yield* enumVisitor( [key, ...context], contract[key], callback); } @@ -194,27 +253,27 @@ function* enumVisitor( } } + /** - * Formats a comment, breaking it into multiple lines and adding a leader to - * each line. + * Create a Rust struct for a given object schema. * - * @param leader The leader to use for each line - * @param comment The comment to format - * @returns The formatted comment + * @param contract The OpenRPC contract that the schema is part of + * @param name The name of the schema + * @param description The description of the schema + * @param properties The properties of the schema + * + * @returns A generator that yields the Rust code for the struct */ -function formatComment(leader: string, comment: string): string { - const lines = formatLines(comment); - let result = ''; - for (const line of lines) { - result += leader + line + '\n'; - } - return result; -} +function* createRustStruct(contract: any, name: string, + description: string, + properties: Record): Generator { -function* createRustStruct(contract: any, name: string, description: string, properties: Record): Generator { + // Create the preamble yield formatComment('/// ', description); yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; + + // Create a field for each property const props = Object.keys(properties); for (let i = 0; i < props.length; i++) { const prop = props[i]; @@ -230,6 +289,15 @@ function* createRustStruct(contract: any, name: string, description: string, pro yield '}\n\n'; } +/** + * Create a Rust comm for a given OpenRPC contract. + * + * @param name The name of the comm + * @param frontend The OpenRPC contract for the frontend + * @param backend The OpenRPC contract for the backend + * + * @returns A generator that yields the Rust code for the comm + */ function* createRustComm(name: string, frontend: any, backend: any): Generator { yield `/*--------------------------------------------------------------------------------------------- * Copyright (C) ${year} Posit Software, PBC. All rights reserved. @@ -258,6 +326,7 @@ use serde::Serialize; } for (const source of [backend, frontend]) { + // Create objects for all the shared components if (!source) { continue; } @@ -299,6 +368,7 @@ use serde::Serialize; }); } + // Create parameter objects for each method for (const source of [backend, frontend]) { if (!source) { continue; @@ -332,6 +402,7 @@ use serde::Serialize; } } + // Create the RPC request and reply enums if (backend) { yield '/**\n'; yield ` * RPC request types for the ${name} comm\n`; @@ -380,6 +451,7 @@ use serde::Serialize; yield `}\n\n`; } + // Create the event enum if (frontend) { yield '/**\n'; yield ` * Front-end events for the ${name} comm\n`; @@ -400,15 +472,35 @@ use serde::Serialize; } } -function* createPythonDataclass(contract: any, name: string, description: string, properties: Record): Generator { +/** + * Create a Python dataclass for a given object schema. + * + * @param contract The OpenRPC contract that the schema is part of + * @param name The name of the schema + * @param description The description of the schema + * @param properties The properties of the schema + * + * @returns A generator that yields the Python code for a dataclass representing + * the schema + */ +function* createPythonDataclass(contract: any, + name: string, + description: string, + properties: Record): Generator { + + // Preamble yield '@dataclass\n'; yield `class ${snakeCaseToSentenceCase(name)}:\n`; + + // Docstring if (description) { yield ' """\n'; yield formatComment(' ', description); yield ' """\n'; yield '\n'; } + + // Fields for (const prop of Object.keys(properties)) { const schema = properties[prop]; yield ` ${prop}: ${deriveType(contract, PythonTypeMap, schema)}`; @@ -422,7 +514,18 @@ function* createPythonDataclass(contract: any, name: string, description: string } -function* createPythonComm(name: string, frontend: any, backend: any): Generator { +/** + * Create a Python comm for a given OpenRPC contract. + * + * @param name The name of the comm + * @param frontend The OpenRPC contract for the frontend + * @param backend The OpenRPC contract for the backend + * + * @returns A generator that yields the Python code for the comm + */ +function* createPythonComm(name: string, + frontend: any, + backend: any): Generator { yield `# # Copyright (C) ${year} Posit Software, PBC. All rights reserved. # @@ -450,6 +553,7 @@ from dataclasses import dataclass, field } for (const source of [backend, frontend]) { + // Create classes for all the shared components if (!source) { continue; } @@ -610,8 +714,20 @@ from dataclasses import dataclass, field } } +/** + * Generates a Typescript interface for a given object schema. + * + * @param contract The OpenRPC contract that the schema is part of + * @param name The name of the schema + * @param description The description of the schema + * @param properties The properties of the schema + * + * @returns A generator that yields the Typescript code for an interface + * representing the schema + */ async function* createTypescriptInterface(contract: any, name: string, - description: string, properties: Record) { + description: string, + properties: Record) { if (!description) { throw new Error(`No description for '${name}'; please add a description to the schema`); @@ -644,6 +760,13 @@ async function* createTypescriptInterface(contract: any, name: string, yield '}\n\n'; } +/** + * Create a Typescript comm for a given OpenRPC contract. + * + * @param name The name of the comm + * @param frontend The OpenRPC contract for the frontend + * @param backend The OpenRPC contract for the backend + */ async function* createTypescriptComm(name: string, frontend: any, backend: any): AsyncGenerator { // Read the metadata file const metadata: CommMetadata = JSON.parse( From 63b6fe40b01224b6470dc38381d197a7e980ba09 Mon Sep 17 00:00:00 2001 From: seem Date: Wed, 20 Dec 2023 18:06:40 +0200 Subject: [PATCH 33/71] pass enter key events on multiline statements to the code editor widget Addresses https://github.com/posit-dev/positron/issues/1552. --- .../browser/components/consoleInput.tsx | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx b/src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx index ef862510224..0821fdf9cd2 100644 --- a/src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx +++ b/src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx @@ -96,6 +96,7 @@ export const ConsoleInput = (props: ConsoleInputProps) => { /** * Executes the code editor widget's code, if possible. + * @returns A Promise that indicates whether the code was executed. */ const executeCodeEditorWidgetCodeIfPossible = async () => { // Get the code from the code editor widget. @@ -110,13 +111,8 @@ export const ConsoleInput = (props: ConsoleInputProps) => { // If the code fragment is incomplete, don't do anything. The user will just see a new // line in the input area. case RuntimeCodeFragmentStatus.Incomplete: { - // For the moment, this works. Ideally, we'd like to have the current code fragment - // prettied up by the runtime and updated. - const updatedCodeFragment = code + '\n'; - setCurrentCodeFragment(updatedCodeFragment); - codeEditorWidgetRef.current.setValue(updatedCodeFragment); - updateCodeEditorWidgetPosition(Position.Last, Position.Last); - return; + // Don't execute the code, let the code editor widget handle the key event. + return false; } // If the code fragment is invalid (contains syntax errors), log a warning but execute @@ -142,6 +138,8 @@ export const ConsoleInput = (props: ConsoleInputProps) => { // Reset the code input state. setCurrentCodeFragment(undefined); codeEditorWidgetRef.current.setValue(''); + + return true; }; // Key down event handler. @@ -165,6 +163,7 @@ export const ConsoleInput = (props: ConsoleInputProps) => { // 'scoped' contexts makes that challenging to access here, and I // haven't figured out the 'right' way to get access to those contexts. if (!cmdOrCtrlKey) { + // eslint-disable-next-line no-restricted-syntax const suggestWidgets = document.getElementsByClassName('suggest-widget'); for (const suggestWidget of suggestWidgets) { if (suggestWidget.classList.contains('visible')) { @@ -298,16 +297,9 @@ export const ConsoleInput = (props: ConsoleInputProps) => { // Enter processing. case KeyCode.Enter: { - // Consume the event. - consumeEvent(); - // If the shift key is pressed, do not process the event because the user is // entering multiple lines. if (e.shiftKey) { - codeEditorWidgetRef.current.setValue( - codeEditorWidgetRef.current.getValue() + '\n' - ); - updateCodeEditorWidgetPosition(Position.Last, Position.Last); return; } @@ -317,7 +309,12 @@ export const ConsoleInput = (props: ConsoleInputProps) => { } // Try to execute the code editor widget's code. - await executeCodeEditorWidgetCodeIfPossible(); + if (await executeCodeEditorWidgetCodeIfPossible()) { + // Only consume the event if the code was executed. Otherwise, let the code + // editor widget handle the key event. + consumeEvent(); + } + break; } } From c02945d0ec2ac5b2d32d269a8d845cda7001aa16 Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Wed, 20 Dec 2023 11:46:38 -0500 Subject: [PATCH 34/71] Beginning of remembering state in PositronDataToolInstance --- .../browser/components/consoleInstance.tsx | 8 ++- .../actionBarComponents/layoutMenuButton.tsx | 10 +-- .../dataToolComponents/columnsPanel.css | 2 + .../dataToolComponents/columnsPanel.tsx | 35 +++++++++- .../dataToolComponents/rowsPanel.css | 2 + .../dataToolComponents/rowsPanel.tsx | 39 ++++++++++- .../browser/components/dataToolPanel.css | 8 ++- .../browser/components/dataToolPanel.tsx | 22 +++--- .../browser/positronDataToolContext.tsx | 4 +- .../browser/positronDataToolEditor.tsx | 16 +++-- .../browser/positronDataToolState.tsx | 2 +- .../interfaces/positronDataToolService.ts | 20 ++++++ .../browser/positronDataToolService.ts | 68 ++++++++++++++++++- 13 files changed, 197 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/positronConsole/browser/components/consoleInstance.tsx b/src/vs/workbench/contrib/positronConsole/browser/components/consoleInstance.tsx index 156b670dbb0..31730c7e059 100644 --- a/src/vs/workbench/contrib/positronConsole/browser/components/consoleInstance.tsx +++ b/src/vs/workbench/contrib/positronConsole/browser/components/consoleInstance.tsx @@ -6,6 +6,7 @@ import 'vs/css!./consoleInstance'; import * as React from 'react'; import { KeyboardEvent, MouseEvent, UIEvent, useEffect, useLayoutEffect, useRef, useState, WheelEvent } from 'react'; // eslint-disable-line no-duplicate-imports import * as nls from 'vs/nls'; +import * as DOM from 'vs/base/browser/dom'; import { generateUuid } from 'vs/base/common/uuid'; import { PixelRatio } from 'vs/base/browser/browser'; import { isMacintosh } from 'vs/base/common/platform'; @@ -98,7 +99,7 @@ export const ConsoleInstance = (props: ConsoleInstanceProps) => { */ const getSelection = () => { // Get the selection. - const selection = document.getSelection(); + const selection = DOM.getActiveWindow().document.getSelection(); if (selection) { // If the selection is outside the element, return null. for (let i = 0; i < selection.rangeCount; i++) { @@ -201,7 +202,7 @@ export const ConsoleInstance = (props: ConsoleInstanceProps) => { * selectAllRuntimeItems selects all runtime items. */ const selectAllRuntimeItems = () => { - const selection = document.getSelection(); + const selection = DOM.getActiveWindow().document.getSelection(); if (selection) { selection.selectAllChildren(runtimeItemsRef.current); } @@ -492,7 +493,8 @@ export const ConsoleInstance = (props: ConsoleInstanceProps) => { const scrollPosition = Math.abs( consoleInstanceRef.current.scrollHeight - consoleInstanceRef.current.clientHeight - - consoleInstanceRef.current.scrollTop); + consoleInstanceRef.current.scrollTop + ); // Update scroll lock. setScrollLock(scrollPosition >= 1); diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx index 6ad6008182c..7c0e3323554 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx @@ -30,7 +30,7 @@ export const LayoutMenuButton = () => { // Builds the actions. const actions = () => { // Get the current layout. - const layout = positronDataToolContext.positronDataToolInstance.layout; + const layout = positronDataToolContext.instance.layout; // Build the actions. const actions: IAction[] = []; @@ -44,7 +44,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsLeft, run: () => { - positronDataToolContext.positronDataToolInstance.layout = PositronDataToolLayout.ColumnsLeft; + positronDataToolContext.instance.layout = PositronDataToolLayout.ColumnsLeft; } }); @@ -57,7 +57,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsRight, run: () => { - positronDataToolContext.positronDataToolInstance.layout = PositronDataToolLayout.ColumnsRight; + positronDataToolContext.instance.layout = PositronDataToolLayout.ColumnsRight; } }); @@ -73,7 +73,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsHidden, run: () => { - positronDataToolContext.positronDataToolInstance.layout = PositronDataToolLayout.ColumnsHidden; + positronDataToolContext.instance.layout = PositronDataToolLayout.ColumnsHidden; } }); @@ -86,7 +86,7 @@ export const LayoutMenuButton = () => { * @returns The icon ID for the layout. */ const selectIconId = () => { - switch (positronDataToolContext.positronDataToolInstance.layout) { + switch (positronDataToolContext.instance.layout) { // Columns left. case PositronDataToolLayout.ColumnsLeft: return 'positron-data-tool-columns-left'; diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.css b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.css index 7b0d4a2b4c2..0e96b59ea12 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.css +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.css @@ -3,6 +3,8 @@ *--------------------------------------------------------------------------------------------*/ .data-tool-panel .columns-panel { + /* This is the scroll container. */ + overflow-y: scroll; } .data-tool-panel .columns-panel .title { diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.tsx index 7a16960da65..4ba796fd708 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.tsx @@ -4,7 +4,9 @@ import 'vs/css!./columnsPanel'; import * as React from 'react'; +import { UIEvent, useEffect, useRef, useState } from 'react'; // eslint-disable-line no-duplicate-imports import { generateUuid } from 'vs/base/common/uuid'; +import { usePositronDataToolContext } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolContext'; /** * ColumnsPanelProps interface. @@ -41,8 +43,39 @@ for (let i = 0; i < 100; i++) { * @returns The rendered component. */ export const ColumnsPanel = (props: ColumnsPanelProps) => { + // Context hooks. + const context = usePositronDataToolContext(); + + // Reference hooks. + const columnsPanel = useRef(undefined!); + + const [columnsScrollPosition, setcolumnsScrollPosition] = useState(undefined); + + useEffect(() => { + setcolumnsScrollPosition(context.instance.columnsScrollPosition); + }, []); + + useEffect(() => { + if (columnsPanel.current && columnsScrollPosition) { + setTimeout(() => { + columnsPanel.current.scrollBy(0, columnsScrollPosition); + }, 100); + } + + }, [columnsPanel, columnsScrollPosition]); + + /** + * onScroll event handler. + * @param e A UIEvent that describes a user interaction with the mouse. + */ + const scrollHandler = (e: UIEvent) => { + // Set the scroll position. + context.instance.columnsScrollPosition = columnsPanel.current.scrollTop; + }; + + // Render. return ( -
+
{dummyColumns.map(column =>
{column.name}
)} diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.css b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.css index 929ae3730ed..bc232a761d5 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.css +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.css @@ -3,6 +3,8 @@ *--------------------------------------------------------------------------------------------*/ .data-tool-panel .rows-panel { + /* This is the scroll container. */ + overflow-y: scroll; } .data-tool-panel .rows-panel .title { diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.tsx index bef16e4335d..43f7be08312 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.tsx @@ -4,12 +4,14 @@ import 'vs/css!./rowsPanel'; import * as React from 'react'; +import { UIEvent, useEffect, useRef } from 'react'; // eslint-disable-line no-duplicate-imports import { generateUuid } from 'vs/base/common/uuid'; +import { usePositronDataToolContext } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolContext'; /** * RowsPanelProps interface. */ -interface ColumnsPanelProps { +interface RowsPanelProps { } /** @@ -40,9 +42,40 @@ for (let i = 0; i < 100; i++) { * @param props A RowsPanelProps that contains the component properties. * @returns The rendered component. */ -export const RowsPanel = (props: ColumnsPanelProps) => { +export const RowsPanel = (props: RowsPanelProps) => { + // Context hooks. + const context = usePositronDataToolContext(); + + // Reference hooks. + const rowsPanel = useRef(undefined!); + + // Main useEffect. + useEffect(() => { + // console.log(`rowsPanel is ${rowsPanel} and rowsScrollPosition is ${context.instance.rowsScrollPosition}`); + }, []); + + /** + * onScroll event handler. + * @param e A UIEvent that describes a user interaction with the mouse. + */ + const scrollHandler = (e: UIEvent) => { + // // Calculate the scroll position. + // const scrollPosition = Math.abs( + // rowsPanel.current.scrollHeight - + // rowsPanel.current.clientHeight - + // rowsPanel.current.scrollTop + // ); + + // Set the scroll position. + // context.instance.rowsScrollPosition = scrollPosition; + + // Log. + // console.log(`Scroll position ${scrollPosition}`); + }; + + // Render. return ( -
+
{dummyRows.map(row =>
{row.name}
)} diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.css b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.css index 7489278a9dc..4edf8107d7e 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.css +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.css @@ -25,8 +25,8 @@ border-radius: 4px; border: 1px solid var(--vscode-positronDataTool-border); - /* This is the scroll container. */ - overflow-y: scroll; + /* Prevent 1fr from exceeding the parent height. */ + min-height: 0; /* Initially hidden. Layout happens in code. */ display: none; @@ -42,7 +42,9 @@ border-radius: 4px; border: 1px solid var(--vscode-positronDataTool-border); + /* Prevent 1fr from exceeding the parent height. */ + min-height: 0; + /* Initially hidden. Layout happens in code. */ display: none; - overflow-y: scroll; } diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx index 329db238ae0..af91094fca1 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx @@ -35,7 +35,7 @@ interface DataToolPanelProps extends PositronDataToolProps { */ export const DataToolPanel = (props: DataToolPanelProps) => { // Context hooks. - const positronDataToolContext = usePositronDataToolContext(); + const context = usePositronDataToolContext(); // Reference hooks. const dataToolPanel = useRef(undefined!); @@ -44,12 +44,9 @@ export const DataToolPanel = (props: DataToolPanelProps) => { const column2 = useRef(undefined!); // State hooks. - const [layout, setLayout] = useState(positronDataToolContext.positronDataToolInstance.layout); + const [layout, setLayout] = useState(context.instance.layout); const [columnsWidth, setColumnsWidth] = useState( - Math.max( - positronDataToolContext.positronDataToolInstance.columnsWidthPercent * props.width, - MIN_COLUMN_WIDTH - ) + Math.max(context.instance.columnsWidthPercent * props.width, MIN_COLUMN_WIDTH) ); // Main useEffect. @@ -58,7 +55,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { const disposableStore = new DisposableStore(); // Add the onDidChangeLayout event handler. - disposableStore.add(positronDataToolContext.positronDataToolInstance.onDidChangeLayout(layout => { + disposableStore.add(context.instance.onDidChangeLayout(layout => { setLayout(layout); })); @@ -76,7 +73,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { column1.current.style.gridRow = 'main / end'; column1.current.style.gridColumn = 'column-1 / splitter'; - column1.current.style.display = 'inline'; + column1.current.style.display = 'grid'; splitter.current.style.gridRow = 'main / end'; splitter.current.style.gridColumn = 'splitter / column-2'; @@ -84,7 +81,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { column2.current.style.gridRow = 'main / end'; column2.current.style.gridColumn = 'column-2 / end'; - column2.current.style.display = 'inline'; + column2.current.style.display = 'grid'; break; // Columns right. @@ -94,7 +91,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { column1.current.style.gridRow = 'main / end'; column1.current.style.gridColumn = 'column-2 / end'; - column1.current.style.display = 'inline'; + column1.current.style.display = 'grid'; splitter.current.style.gridRow = 'main / end'; splitter.current.style.gridColumn = 'splitter / column-2'; @@ -102,7 +99,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { column2.current.style.gridRow = 'main / end'; column2.current.style.gridColumn = 'column-1 / splitter'; - column2.current.style.display = 'inline'; + column2.current.style.display = 'grid'; break; // Columns hidden. @@ -127,7 +124,6 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // Width effect. useEffect(() => { - console.log(`Width changed useEffect is running width is now ${props.width}`); }, [props.width]); /** @@ -171,7 +167,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // Update the the column width and return ColumnSplitterResizeResult.Resizing to get the // cursor updated. setColumnsWidth(newColumnWidth); - positronDataToolContext.positronDataToolInstance.columnsWidthPercent = newColumnWidth / props.width; + context.instance.columnsWidthPercent = newColumnWidth / props.width; return PositronColumnSplitterResizeResult.Resizing; }; diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolContext.tsx b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolContext.tsx index 28cbfc9cfb2..dccf061d858 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolContext.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolContext.tsx @@ -18,11 +18,11 @@ export const PositronDataToolContextProvider = ( props: PropsWithChildren ) => { // State hooks. - const positronDataToolState = usePositronDataToolState(props); + const state = usePositronDataToolState(props); // Render. return ( - + {props.children} ); diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx index 310946ac499..e59c19a785f 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx @@ -220,7 +220,7 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen */ protected override createEditor(parent: HTMLElement): void { // Logging. - console.log(`PositronDataEditor ${this.instance} createEdtitor`); + console.log(`PositronDataEditor ${this.instance} createEditor`); // Create and append the Positron data tool container. this._positronDataToolsContainer = DOM.$('.positron-data-tool-container'); @@ -243,6 +243,9 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen // Call the base class's method. await super.setInput(input, options, context, token); + // Logging. + console.log(`PositronDataEditor ${this.instance} setInput ${input.resource}`); + // Parse the Positron data tool URI. const identifier = PositronDataToolUri.parse(input.resource); if (identifier) { @@ -261,20 +264,21 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen contextKeyService={this._contextKeyService} contextMenuService={this._contextMenuService} keybindingService={this._keybindingService} - positronDataToolInstance={positronDataToolInstance} + instance={positronDataToolInstance} reactComponentContainer={this} /> ); + // Logging. + console.log(`PositronDataEditor ${this.instance} create PositronReactRenderer`); + + // Success. return; } } // TODO: Render some kind of an error. - - // Logging. - console.log(`PositronDataEditor ${this.instance} setInput ${input.resource}`); } /** @@ -340,7 +344,7 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen // the PositronDataTool from the DOM. if (this._positronReactRenderer) { // Logging. - console.log(`PositronDataEditor ${this.instance} disposePositronReactRenderer`); + console.log(`PositronDataEditor ${this.instance} dispose PositronReactRenderer`); // Dispose of the PositronReactRenderer for the PositronDataTool. this._positronReactRenderer.dispose(); diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolState.tsx b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolState.tsx index de3ec81cc2a..315f0adec69 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolState.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolState.tsx @@ -19,7 +19,7 @@ export interface PositronDataToolServices extends PositronActionBarServices { * PositronDataToolConfiguration interface. */ export interface PositronDataToolConfiguration extends PositronDataToolServices { - readonly positronDataToolInstance: IPositronDataToolInstance; + readonly instance: IPositronDataToolInstance; } /** diff --git a/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts index 25ca795f6e8..a1ddadeb7b1 100644 --- a/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts +++ b/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts @@ -58,6 +58,16 @@ export interface IPositronDataToolInstance { */ columnsWidthPercent: number; + /** + * Gets or sets the columns scroll position. + */ + columnsScrollPosition: number; + + /** + * Gets or sets the rows scroll position. + */ + rowsScrollPosition: number; + /** * The onDidChangeLayout event. */ @@ -67,4 +77,14 @@ export interface IPositronDataToolInstance { * The onDidChangeColumnsWidthPercent event. */ readonly onDidChangeColumnsWidthPercent: Event; + + /** + * The onDidChangeColumnsScrollPosition event. + */ + readonly onDidChangeColumnsScrollPosition: Event; + + /** + * The onDidChangeRowsScrollPosition event. + */ + readonly onDidChangeRowsScrollPosition: Event; } diff --git a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts index f42600e01e5..ff87c9710bc 100644 --- a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts +++ b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts @@ -87,6 +87,16 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn */ private _columnsWidthPercent = 0.25; + /** + * Gets or sets the columns scroll position. + */ + private _columnsScrollPosition = 0; + + /** + * Gets or sets the rows scroll position. + */ + private _rowsScrollPosition = 0; + /** * The onDidChangeLayout event emitter. */ @@ -98,6 +108,16 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn */ private readonly _onDidChangeColumnsWidthPercentEmitter = this._register(new Emitter); + /** + * The onDidChangeColumnsScrollPositon event emitter. + */ + private readonly _onDidChangeColumnsScrollPositionEmitter = this._register(new Emitter); + + /** + * The onDidChangeRowsScrollPositon event emitter. + */ + private readonly _onDidChangeRowsScrollPositionEmitter = this._register(new Emitter); + //#endregion Private Properties //#region Constructor & Dispose @@ -133,14 +153,14 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn } /** - * Gets the columns width. + * Gets the columns width percent. */ get columnsWidthPercent() { return this._columnsWidthPercent; } /** - * Sets the columns width. + * Sets the columns width percent. */ set columnsWidthPercent(columnsWidthPercent: number) { if (columnsWidthPercent !== this._columnsWidthPercent) { @@ -149,6 +169,40 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn } } + /** + * Gets the columns scroll position. + */ + get columnsScrollPosition() { + return this._columnsScrollPosition; + } + + /** + * Sets the columns scroll position. + */ + set columnsScrollPosition(columnsScrollPosition: number) { + if (columnsScrollPosition !== this._columnsScrollPosition) { + this._columnsScrollPosition = columnsScrollPosition; + this._onDidChangeColumnsScrollPositionEmitter.fire(this._columnsScrollPosition); + } + } + + /** + * Gets the rows scroll position. + */ + get rowsScrollPosition() { + return this._rowsScrollPosition; + } + + /** + * Sets the rows scroll position. + */ + set rowsScrollPosition(rowsScrollPosition: number) { + if (rowsScrollPosition !== this._rowsScrollPosition) { + this._rowsScrollPosition = rowsScrollPosition; + this._onDidChangeRowsScrollPositionEmitter.fire(this._rowsScrollPosition); + } + } + /** * onDidChangeLayout event. */ @@ -159,6 +213,16 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn */ readonly onDidChangeColumnsWidthPercent = this._onDidChangeColumnsWidthPercentEmitter.event; + /** + * onDidChangeColumnsScrollPosition event. + */ + readonly onDidChangeColumnsScrollPosition = this._onDidChangeColumnsScrollPositionEmitter.event; + + /** + * onDidChangeRowsScrollPosition event. + */ + readonly onDidChangeRowsScrollPosition = this._onDidChangeRowsScrollPositionEmitter.event; + //#endregion IPositronDataToolInstance Implementation } From 4c9d798d33412683a5aaf47b9cc20cd2609d6edc Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Wed, 20 Dec 2023 12:20:47 -0500 Subject: [PATCH 35/71] A little refactoring --- .../positronDataTool/browser/positronDataToolService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts index ff87c9710bc..ad7603c6e45 100644 --- a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts +++ b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts @@ -33,8 +33,6 @@ class PositronDataToolService extends Disposable implements IPositronDataToolSer ) { // Call the disposable constrcutor. super(); - - this._positronDataToolInstanceMap.entries(); } //#endregion Constructor & Dispose @@ -50,19 +48,21 @@ class PositronDataToolService extends Disposable implements IPositronDataToolSer * Test open function. */ async testOpen(identifier: string): Promise { + // Add the instance, if necessary. if (!this._positronDataToolInstanceMap.has(identifier)) { const positronDataToolInstance = new PositronDataToolInstance(identifier); this._positronDataToolInstanceMap.set(identifier, positronDataToolInstance); } + // Open the editor. await this._editorService.openEditor({ resource: PositronDataToolUri.generate(identifier) }); } /** - * - * @param identifier + * Gets a Positron data tool instance. + * @param identifier The identifier of the Positron data tool instance. */ getInstance(identifier: string): IPositronDataToolInstance | undefined { return this._positronDataToolInstanceMap.get(identifier); From eb8c4b7514ff9a762758a0dea7b9bf2cef6e393f Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 09:46:52 -0800 Subject: [PATCH 36/71] generate Rust type aliases --- positron/comms/generate-comms.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 50227cc519a..f6c9d0b985f 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -337,6 +337,11 @@ use serde::Serialize; yield* createRustStruct(backend, key, schema.description, schema.properties); + } else { + yield formatComment('/// ', schema.description); + yield `type ${snakeCaseToSentenceCase(key)} = `; + yield deriveType(source, RustTypeMap, schema); + yield ';\n\n'; } } } From 224c26c2338fa163bfc9931dc9e8ef9ce87f86ec Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 12:11:43 -0800 Subject: [PATCH 37/71] create object visitor to discover nested object types --- positron/comms/generate-comms.ts | 156 ++++++++++++------ .../common/positronDataToolComm.ts | 2 +- 2 files changed, 103 insertions(+), 55 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index f6c9d0b985f..6bba1f60f96 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -130,28 +130,28 @@ function parseRef(ref: string, contract: any): string { * * @param contract The OpenRPC contract that the schema is part of * @param typeMap A map from schema types to language types + * @param key The key beneath which this schema is defined * @param schema The schema to derive a type from * * @returns A string containing the derived type */ function deriveType(contract: any, typeMap: Record, + key: string, schema: any): string { if (schema.type === 'array') { - if (schema.items.$ref) { - // If the array has a ref, use that to derive an array type - return typeMap['array-begin'] + - parseRef(schema.items.$ref, contract) + - typeMap['array-end']; + // If the array has a ref, use that to derive an array type + return typeMap['array-begin'] + + deriveType(contract, typeMap, key, schema.items) + + typeMap['array-end']; + } else if (schema.$ref) { + return parseRef(schema.$ref, contract); + } else if (schema.type === 'object') { + if (schema.name) { + return snakeCaseToSentenceCase(schema.name); } else { - // Otherwise use the type of the items directly - return typeMap['array-begin'] + - typeMap[schema.items.type] + - typeMap['array-end']; + return snakeCaseToSentenceCase(key); } - } else if (schema.type === 'object' && schema.$ref) { - // If the object has a ref, use that to derive an object type - return parseRef(schema.$ref, contract); } else { if (Object.keys(typeMap).includes(schema.type)) { return typeMap[schema.type]; @@ -254,6 +254,50 @@ function* enumVisitor( } +/** + * Visitor function for object definitions (i.e. schema type = "object") in an + * OpenRPC contract. Recursively discovers all object definitions and invokes + * the callback for each one. + * + * @param context The current context stack (names of keys leading to the object) + * @param contract The OpenRPC contract to visit + * @param callback The callback function to call for each object + * + * @returns An generator that yields the results of the callback function, + * invoked for each object definition + */ +function* objectVisitor( + context: Array, + contract: any, + callback: (context: Array, o: Record) => Generator +): Generator { + if (contract.type === 'object') { + // This is an object definition, so call the callback function and yield + // the result + yield* callback(context, contract); + + // Keep recursing into the object definition to discover any nested + // object definitions + yield* objectVisitor(context, contract.properties, callback); + } else if (Array.isArray(contract)) { + // If this object is an array, recurse into each item + for (const item of contract) { + yield* objectVisitor(context, item, callback); + } + } else if (typeof contract === 'object') { + // If this object is an object, recurse into each property + for (const key of Object.keys(contract)) { + if (key === 'schema') { + yield* objectVisitor(context, contract[key], callback); + } + else { + yield* objectVisitor( + [key, ...context], contract[key], callback); + } + } + } +} + /** * Create a Rust struct for a given object schema. * @@ -281,7 +325,7 @@ function* createRustStruct(contract: any, name: string, if (schema.description) { yield formatComment('\t/// ', schema.description); } - yield `\tpub ${prop}: ${deriveType(contract, RustTypeMap, schema)},\n`; + yield `\tpub ${prop}: ${deriveType(contract, RustTypeMap, prop, schema)},\n`; if (i < props.length - 1) { yield '\n'; } @@ -312,46 +356,50 @@ use serde::Serialize; `; - if (backend) { - // Create objects for all the object schemas first - for (const method of backend.methods) { - if (method.result && - method.result.schema && - method.result.schema.type === 'object') { - yield* createRustStruct(backend, method.result.schema.name, - method.result.schema.description, - method.result.schema.properties); - } - } - } - for (const source of [backend, frontend]) { - // Create objects for all the shared components if (!source) { continue; } + + // Create type aliases for all the shared types if (source.components && source.components.schemas) { for (const key of Object.keys(backend.components.schemas)) { const schema = backend.components.schemas[key]; - if (schema.type === 'object') { - yield* createRustStruct(backend, key, - schema.description, - schema.properties); - } else { + if (schema.type !== 'object') { yield formatComment('/// ', schema.description); yield `type ${snakeCaseToSentenceCase(key)} = `; - yield deriveType(source, RustTypeMap, schema); + yield deriveType(source, RustTypeMap, + schema.name ? schema.name : key, + schema); yield ';\n\n'; } } } - } - // Create enums for all enum types - for (const source of [backend, frontend]) { - if (!source) { - continue; - } + // Create structs for all object types + yield* objectVisitor([], source, function* (context: Array, o: Record) { + if (o.description) { + yield formatComment('/// ', o.description); + } else { + yield formatComment('/// ', + snakeCaseToSentenceCase(context[0]) + ' in ' + + snakeCaseToSentenceCase(context[1])); + } + const name = o.name ? o.name : context[0] === 'items' ? context[1] : context[0]; + yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; + yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; + for (const key of Object.keys(o.properties)) { + const prop = o.properties[key]; + if (prop.description) { + yield formatComment('\t/// ', prop.description); + } + yield `\tpub ${key}: ${deriveType(source, RustTypeMap, key, prop)},\n\n`; + } + yield '}\n\n'; + }); + + + // Create enums for all enum types yield* enumVisitor([], source, function* (context: Array, values: Array) { yield formatComment(`/// `, `Possible values for ` + @@ -396,7 +444,7 @@ use serde::Serialize; yield `\tpub ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)},\n`; } else { // Otherwise use the type directly - yield `\tpub ${param.name}: ${deriveType(source, RustTypeMap, param.schema)},\n`; + yield `\tpub ${param.name}: ${deriveType(source, RustTypeMap, param.name, param.schema)},\n`; } if (i < method.params.length - 1) { yield '\n'; @@ -426,9 +474,9 @@ use serde::Serialize; yield `\t#[serde(rename = "${method.name}")]\n`; yield `\t${snakeCaseToSentenceCase(method.name)}`; if (method.params.length > 0) { - yield `(${snakeCaseToSentenceCase(method.name)}Params),\n`; + yield `(${snakeCaseToSentenceCase(method.name)}Params),\n\n`; } else { - yield ',\n'; + yield ',\n\n'; } } yield `}\n\n`; @@ -447,9 +495,9 @@ use serde::Serialize; } yield `\t${snakeCaseToSentenceCase(method.name)}Reply`; if (schema.type === 'object') { - yield `(${snakeCaseToSentenceCase(schema.name)}),\n`; + yield `(${snakeCaseToSentenceCase(schema.name)}),\n\n`; } else { - yield `(${RustTypeMap[schema.type]}),\n`; + yield `(${RustTypeMap[schema.type]}),\n\n`; } } } @@ -468,9 +516,9 @@ use serde::Serialize; yield `\t#[serde(rename = "${method.name}")]\n`; yield `\t${snakeCaseToSentenceCase(method.name)}`; if (method.params.length > 0) { - yield `(${snakeCaseToSentenceCase(method.name)}Params),\n`; + yield `(${snakeCaseToSentenceCase(method.name)}Params),\n\n`; } else { - yield ',\n'; + yield ',\n\n'; } } yield `}\n\n`; @@ -508,7 +556,7 @@ function* createPythonDataclass(contract: any, // Fields for (const prop of Object.keys(properties)) { const schema = properties[prop]; - yield ` ${prop}: ${deriveType(contract, PythonTypeMap, schema)}`; + yield ` ${prop}: ${deriveType(contract, PythonTypeMap, prop, schema)}`; yield ' = field(\n'; yield ` metadata={\n`; yield ` "description": "${schema.description}",\n`; @@ -635,7 +683,7 @@ from dataclasses import dataclass, field if (param.schema.enum) { yield ` ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield ` ${param.name}: ${deriveType(backend, PythonTypeMap, param.schema)}`; + yield ` ${param.name}: ${deriveType(backend, PythonTypeMap, param.name, param.schema)}`; } yield ' = field(\n'; yield ` metadata={\n`; @@ -706,7 +754,7 @@ from dataclasses import dataclass, field if (param.schema.enum) { yield ` ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield ` ${param.name}: ${deriveType(backend, PythonTypeMap, param.schema)}`; + yield ` ${param.name}: ${deriveType(backend, PythonTypeMap, param.name, param.schema)}`; } yield ' = field(\n'; yield ` metadata={\n`; @@ -758,7 +806,7 @@ async function* createTypescriptInterface(contract: any, name: string, } else if (schema.type === 'string' && schema.enum) { yield `${snakeCaseToSentenceCase(name)}${snakeCaseToSentenceCase(prop)}`; } else { - yield deriveType(contract, TypescriptTypeMap, schema); + yield deriveType(contract, TypescriptTypeMap, prop, schema); } yield `;\n\n`; } @@ -845,7 +893,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co yield formatComment(' * ', schema.description); yield ' */\n'; yield `export type ${snakeCaseToSentenceCase(key)} = `; - yield deriveType(source, TypescriptTypeMap, schema); + yield deriveType(source, TypescriptTypeMap, key, schema); yield ';\n\n'; } } @@ -870,7 +918,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (param.schema.type === 'string' && param.schema.enum) { yield `${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield deriveType(frontend, TypescriptTypeMap, param.schema); + yield deriveType(frontend, TypescriptTypeMap, param.name, param.schema); } yield `;\n\n`; } @@ -937,7 +985,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (schema.type === 'string' && schema.enum) { yield `${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield deriveType(backend, TypescriptTypeMap, schema); + yield deriveType(backend, TypescriptTypeMap, param.name, schema); } if (i < method.params.length - 1) { yield ', '; @@ -948,7 +996,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (method.result.schema.type === 'object') { yield snakeCaseToSentenceCase(method.result.schema.name); } else { - yield deriveType(backend, TypescriptTypeMap, method.result.schema); + yield deriveType(backend, TypescriptTypeMap, method.name, method.result.schema); } } else { yield 'void'; diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index ad0f97dc387..ce78e4f0e85 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -95,7 +95,7 @@ export interface ProfileResult { /** * Counts of distinct values in column */ - freqtable_counts: Array; + freqtable_counts: Array; /** * Number of other values not accounted for in counts From 8c2f970a70961929e8cb9f5b2754078321e43849 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 12:23:22 -0800 Subject: [PATCH 38/71] recursive object enumeration for python --- positron/comms/generate-comms.ts | 142 ++++++++----------------------- 1 file changed, 36 insertions(+), 106 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 6bba1f60f96..072e2c4fb7f 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -298,41 +298,6 @@ function* objectVisitor( } } -/** - * Create a Rust struct for a given object schema. - * - * @param contract The OpenRPC contract that the schema is part of - * @param name The name of the schema - * @param description The description of the schema - * @param properties The properties of the schema - * - * @returns A generator that yields the Rust code for the struct - */ -function* createRustStruct(contract: any, name: string, - description: string, - properties: Record): Generator { - - // Create the preamble - yield formatComment('/// ', description); - yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; - yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; - - // Create a field for each property - const props = Object.keys(properties); - for (let i = 0; i < props.length; i++) { - const prop = props[i]; - const schema = properties[prop]; - if (schema.description) { - yield formatComment('\t/// ', schema.description); - } - yield `\tpub ${prop}: ${deriveType(contract, RustTypeMap, prop, schema)},\n`; - if (i < props.length - 1) { - yield '\n'; - } - } - yield '}\n\n'; -} - /** * Create a Rust comm for a given OpenRPC contract. * @@ -525,48 +490,6 @@ use serde::Serialize; } } -/** - * Create a Python dataclass for a given object schema. - * - * @param contract The OpenRPC contract that the schema is part of - * @param name The name of the schema - * @param description The description of the schema - * @param properties The properties of the schema - * - * @returns A generator that yields the Python code for a dataclass representing - * the schema - */ -function* createPythonDataclass(contract: any, - name: string, - description: string, - properties: Record): Generator { - - // Preamble - yield '@dataclass\n'; - yield `class ${snakeCaseToSentenceCase(name)}:\n`; - - // Docstring - if (description) { - yield ' """\n'; - yield formatComment(' ', description); - yield ' """\n'; - yield '\n'; - } - - // Fields - for (const prop of Object.keys(properties)) { - const schema = properties[prop]; - yield ` ${prop}: ${deriveType(contract, PythonTypeMap, prop, schema)}`; - yield ' = field(\n'; - yield ` metadata={\n`; - yield ` "description": "${schema.description}",\n`; - yield ` }\n`; - yield ` )\n\n`; - } - yield '\n\n'; -} - - /** * Create a Python comm for a given OpenRPC contract. * @@ -592,41 +515,48 @@ from dataclasses import dataclass, field `; - if (backend) { - // Create classes for all the object schemas first - for (const method of backend.methods) { - if (method.result && - method.result.schema && - method.result.schema.type === 'object') { - yield* createPythonDataclass(backend, method.result.schema.name, - method.result.schema.description, - method.result.schema.properties); - } - } - } - for (const source of [backend, frontend]) { - // Create classes for all the shared components if (!source) { continue; } - if (source.components && source.components.schemas) { - for (const key of Object.keys(backend.components.schemas)) { - const schema = backend.components.schemas[key]; - if (schema.type === 'object') { - yield* createPythonDataclass(backend, key, - schema.description, - schema.properties); - } + // Create dataclasses for all object types + yield* objectVisitor([], source, function* ( + context: Array, + o: Record) { + + // Preamble + yield '@dataclass\n'; + const name = o.name ? o.name : context[0] === 'items' ? context[1] : context[0]; + yield `class ${snakeCaseToSentenceCase(name)}:\n`; + + // Docstring + if (o.description) { + yield ' """\n'; + yield formatComment(' ', o.description); + yield ' """\n'; + yield '\n'; + } else { + yield ' """\n'; + yield formatComment(' ', snakeCaseToSentenceCase(context[0]) + ' in ' + + snakeCaseToSentenceCase(context[1])); + yield ' """\n'; + yield '\n'; } - } - } - // Create enums for all enum types - for (const source of [backend, frontend]) { - if (!source) { - continue; - } + // Fields + for (const prop of Object.keys(o.properties)) { + const schema = o.properties[prop]; + yield ` ${prop}: ${deriveType(source, PythonTypeMap, prop, schema)}`; + yield ' = field(\n'; + yield ` metadata={\n`; + yield ` "description": "${schema.description}",\n`; + yield ` }\n`; + yield ` )\n\n`; + } + yield '\n\n'; + }); + + // Create enums for all enum types yield* enumVisitor([], source, function* (context: Array, values: Array) { yield '@enum.unique\n'; yield `class ${snakeCaseToSentenceCase(context[1])}`; From e6b64f2cf9d3a0802176eba7476df380954bfbb6 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 12:43:09 -0800 Subject: [PATCH 39/71] recursive object enumeration for typescript --- positron/comms/generate-comms.ts | 36 +++--- .../common/positronDataToolComm.ts | 108 ++++++++++-------- .../common/positronVariablesComm.ts | 32 +++--- 3 files changed, 92 insertions(+), 84 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 072e2c4fb7f..823f5b981a8 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -708,9 +708,9 @@ from dataclasses import dataclass, field * @returns A generator that yields the Typescript code for an interface * representing the schema */ -async function* createTypescriptInterface(contract: any, name: string, +function* createTypescriptInterface(contract: any, name: string, description: string, - properties: Record) { + properties: Record): Generator { if (!description) { throw new Error(`No description for '${name}'; please add a description to the schema`); @@ -750,7 +750,7 @@ async function* createTypescriptInterface(contract: any, name: string, * @param frontend The OpenRPC contract for the frontend * @param backend The OpenRPC contract for the backend */ -async function* createTypescriptComm(name: string, frontend: any, backend: any): AsyncGenerator { +function* createTypescriptComm(name: string, frontend: any, backend: any): Generator { // Read the metadata file const metadata: CommMetadata = JSON.parse( readFileSync(path.join(commsDir, `${name}.json`), { encoding: 'utf-8' })); @@ -768,24 +768,20 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co `; - if (backend) { - // Create interfaces for all the object schemas first - for (const method of backend.methods) { - if (method.result && - method.result.schema && - method.result.schema.type === 'object') { - yield* createTypescriptInterface(backend, method.result.schema.name, - method.result.schema.description, - method.result.schema.properties); - } - } - } - - // Create enums for all enum types for (const source of [backend, frontend]) { if (!source) { continue; } + yield* objectVisitor([], source, + function* (context: Array, o: Record): Generator { + const name = o.name ? o.name : context[0] === 'items' ? context[1] : context[0]; + const description = o.description ? o.description : + snakeCaseToSentenceCase(context[0]) + ' in ' + + snakeCaseToSentenceCase(context[1]); + yield* createTypescriptInterface(source, name, description, o.properties); + }); + + // Create enums for all enum types yield* enumVisitor([], source, function* (context: Array, values: Array) { yield '/**\n'; yield formatComment(` * `, @@ -814,11 +810,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (source.components && source.components.schemas) { for (const key of Object.keys(backend.components.schemas)) { const schema = backend.components.schemas[key]; - if (schema.type === 'object') { - yield* createTypescriptInterface(backend, key, - schema.description, - schema.properties); - } else { + if (schema.type !== 'object') { yield `/**\n`; yield formatComment(' * ', schema.description); yield ' */\n'; diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index ce78e4f0e85..958ffb33414 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -105,60 +105,35 @@ export interface ProfileResult { } /** - * The current backend state + * Items in FreqtableCounts */ -export interface BackendState { +export interface FreqtableCounts { /** - * The set of currently applied filters + * Stringified value */ - filters: Array; + value: string; /** - * The set of currently applied sorts + * Number of occurrences of value */ - sort_keys: Array; + count: number; } /** - * Possible values for ProfileType in GetColumnProfile - */ -enum GetColumnProfileProfileType { - Freqtable = 'freqtable', - Histogram = 'histogram' -} - -/** - * Possible values for FilterType in ColumnFilter + * The current backend state */ -enum ColumnFilterFilterType { - Isnull = 'isnull', - Notnull = 'notnull', - Compare = 'compare', - SetMembership = 'set_membership', - Search = 'search' -} +export interface BackendState { + /** + * The set of currently applied filters + */ + filters: Array; -/** - * Possible values for CompareOp in ColumnFilter - */ -enum ColumnFilterCompareOp { - EqEq = '==', - NotEq = '!=', - Lt = '<', - LtEq = '<=', - Gt = '>', - GtEq = '>=' -} + /** + * The set of currently applied sorts + */ + sort_keys: Array; -/** - * Possible values for SearchType in ColumnFilter - */ -enum ColumnFilterSearchType { - Contains = 'contains', - Startswith = 'startswith', - Endswith = 'endswith', - Regex = 'regex' } /** @@ -207,11 +182,6 @@ export interface ColumnSchema { } -/** - * Column values formatted as strings - */ -export type ColumnFormattedData = Array; - /** * Specifies a table row filter based on a column's values */ @@ -301,6 +271,52 @@ export interface ColumnSortKey { } +/** + * Possible values for ProfileType in GetColumnProfile + */ +enum GetColumnProfileProfileType { + Freqtable = 'freqtable', + Histogram = 'histogram' +} + +/** + * Possible values for FilterType in ColumnFilter + */ +enum ColumnFilterFilterType { + Isnull = 'isnull', + Notnull = 'notnull', + Compare = 'compare', + SetMembership = 'set_membership', + Search = 'search' +} + +/** + * Possible values for CompareOp in ColumnFilter + */ +enum ColumnFilterCompareOp { + EqEq = '==', + NotEq = '!=', + Lt = '<', + LtEq = '<=', + Gt = '>', + GtEq = '>=' +} + +/** + * Possible values for SearchType in ColumnFilter + */ +enum ColumnFilterSearchType { + Contains = 'contains', + Startswith = 'startswith', + Endswith = 'endswith', + Regex = 'regex' +} + +/** + * Column values formatted as strings + */ +export type ColumnFormattedData = Array; + export class PositronDataToolComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index 7ec54403a0d..ab95e6cc6bf 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -44,22 +44,6 @@ export interface FormattedVariable { } -/** - * Possible values for Kind in Variable - */ -enum VariableKind { - Boolean = 'boolean', - Bytes = 'bytes', - Collection = 'collection', - Empty = 'empty', - Function = 'function', - Map = 'map', - Number = 'number', - Other = 'other', - String = 'string', - Table = 'table' -} - /** * A single variable in the runtime. */ @@ -121,6 +105,22 @@ export interface Variable { } +/** + * Possible values for Kind in Variable + */ +enum VariableKind { + Boolean = 'boolean', + Bytes = 'bytes', + Collection = 'collection', + Empty = 'empty', + Function = 'function', + Map = 'map', + Number = 'number', + Other = 'other', + String = 'string', + Table = 'table' +} + export class PositronVariablesComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); From f9868b270958d801a321bcbe31d039b4a77d4d9c Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 13:53:42 -0800 Subject: [PATCH 40/71] export enums --- positron/comms/generate-comms.ts | 2 +- .../languageRuntime/common/positronDataToolComm.ts | 8 ++++---- .../services/languageRuntime/common/positronHelpComm.ts | 2 +- .../languageRuntime/common/positronVariablesComm.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 823f5b981a8..6ff032fbd86 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -789,7 +789,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co snakeCaseToSentenceCase(context[0]) + ` in ` + snakeCaseToSentenceCase(context[1])); yield ' */\n'; - yield `enum ${snakeCaseToSentenceCase(context[1])}${snakeCaseToSentenceCase(context[0])} {\n`; + yield `export enum ${snakeCaseToSentenceCase(context[1])}${snakeCaseToSentenceCase(context[0])} {\n`; for (let i = 0; i < values.length; i++) { const value = values[i]; yield `\t${snakeCaseToSentenceCase(value)} = '${value}'`; diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index 958ffb33414..ae7f9128eb4 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -274,7 +274,7 @@ export interface ColumnSortKey { /** * Possible values for ProfileType in GetColumnProfile */ -enum GetColumnProfileProfileType { +export enum GetColumnProfileProfileType { Freqtable = 'freqtable', Histogram = 'histogram' } @@ -282,7 +282,7 @@ enum GetColumnProfileProfileType { /** * Possible values for FilterType in ColumnFilter */ -enum ColumnFilterFilterType { +export enum ColumnFilterFilterType { Isnull = 'isnull', Notnull = 'notnull', Compare = 'compare', @@ -293,7 +293,7 @@ enum ColumnFilterFilterType { /** * Possible values for CompareOp in ColumnFilter */ -enum ColumnFilterCompareOp { +export enum ColumnFilterCompareOp { EqEq = '==', NotEq = '!=', Lt = '<', @@ -305,7 +305,7 @@ enum ColumnFilterCompareOp { /** * Possible values for SearchType in ColumnFilter */ -enum ColumnFilterSearchType { +export enum ColumnFilterSearchType { Contains = 'contains', Startswith = 'startswith', Endswith = 'endswith', diff --git a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts index 02a69dfd84b..acc573a10e5 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts @@ -13,7 +13,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co /** * Possible values for Kind in ShowHelp */ -enum ShowHelpKind { +export enum ShowHelpKind { Html = 'html', Markdown = 'markdown', Url = 'url' diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index ab95e6cc6bf..9d6b6eba449 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -108,7 +108,7 @@ export interface Variable { /** * Possible values for Kind in Variable */ -enum VariableKind { +export enum VariableKind { Boolean = 'boolean', Bytes = 'bytes', Collection = 'collection', From e42fe1fca5aba234850f6cdead9354204573e1ed Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 14:05:19 -0800 Subject: [PATCH 41/71] honor required/optional fields in typescript --- positron/comms/generate-comms.ts | 16 +++-- positron/comms/plot-backend-openrpc.json | 6 +- positron/comms/variables-backend-openrpc.json | 26 ++++++- .../common/positronDataToolComm.ts | 68 +++++++++---------- 4 files changed, 74 insertions(+), 42 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 6ff032fbd86..f1d2ba93201 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -704,13 +704,16 @@ from dataclasses import dataclass, field * @param name The name of the schema * @param description The description of the schema * @param properties The properties of the schema + * @param required An array of required properties * * @returns A generator that yields the Typescript code for an interface * representing the schema */ -function* createTypescriptInterface(contract: any, name: string, +function* createTypescriptInterface(contract: any, + name: string, description: string, - properties: Record): Generator { + properties: Record, + required: Array): Generator { if (!description) { throw new Error(`No description for '${name}'; please add a description to the schema`); @@ -730,7 +733,11 @@ function* createTypescriptInterface(contract: any, name: string, yield '\t/**\n'; yield formatComment('\t * ', schema.description); yield '\t */\n'; - yield `\t${prop}: `; + yield `\t${prop}`; + if (!required.includes(prop)) { + yield '?'; + } + yield `: `; if (schema.type === 'object') { yield snakeCaseToSentenceCase(schema.name); } else if (schema.type === 'string' && schema.enum) { @@ -778,7 +785,8 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co const description = o.description ? o.description : snakeCaseToSentenceCase(context[0]) + ' in ' + snakeCaseToSentenceCase(context[1]); - yield* createTypescriptInterface(source, name, description, o.properties); + yield* createTypescriptInterface(source, name, description, o.properties, + o.required ? o.required : []); }); // Create enums for all enum types diff --git a/positron/comms/plot-backend-openrpc.json b/positron/comms/plot-backend-openrpc.json index 60d360f0b8b..ae607cc8cc9 100644 --- a/positron/comms/plot-backend-openrpc.json +++ b/positron/comms/plot-backend-openrpc.json @@ -46,7 +46,11 @@ "description": "The MIME type of the plot data", "type": "string" } - } + }, + "required": [ + "data", + "mime_type" + ] } } } diff --git a/positron/comms/variables-backend-openrpc.json b/positron/comms/variables-backend-openrpc.json index 2b92c27ca66..0c3a9993d37 100644 --- a/positron/comms/variables-backend-openrpc.json +++ b/positron/comms/variables-backend-openrpc.json @@ -71,7 +71,11 @@ "type": "integer", "description": "The total number of children. This may be greater than the number of children in the 'children' array if the array is truncated." } - } + }, + "required": [ + "children", + "length" + ] } } }, @@ -112,7 +116,11 @@ "type": "string", "description": "The formatted content of the variable." } - } + }, + "required": [ + "format", + "content" + ] } } } @@ -176,7 +184,19 @@ "type": "boolean", "description": "True the 'value' field is a truncated representation of the variable's value" } - } + }, + "required": [ + "access_key", + "display_name", + "display_value", + "display_type", + "type_info", + "kind", + "length", + "has_children", + "has_viewer", + "is_truncated" + ] } } } diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index ae7f9128eb4..5429b97a1b7 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -17,12 +17,12 @@ export interface TableSchema { /** * Schema for each column in the table */ - columns: Array; + columns?: Array; /** * Numbers of rows in the unfiltered dataset */ - num_rows: number; + num_rows?: number; } @@ -38,7 +38,7 @@ export interface TableData { /** * Zero or more arrays of row labels */ - row_labels: Array; + row_labels?: Array; } @@ -49,7 +49,7 @@ export interface FilterResult { /** * Number of rows in table after applying filters */ - selected_num_rows: number; + selected_num_rows?: number; } @@ -65,42 +65,42 @@ export interface ProfileResult { /** * Minimum value as string computed as part of histogram */ - min_value: string; + min_value?: string; /** * Maximum value as string computed as part of histogram */ - max_value: string; + max_value?: string; /** * Average value as string computed as part of histogram */ - mean_value: string; + mean_value?: string; /** * Absolute count of values in each histogram bin */ - histogram_bin_sizes: Array; + histogram_bin_sizes?: Array; /** * Absolute floating-point width of a histogram bin */ - histogram_bin_width: number; + histogram_bin_width?: number; /** * Quantile values computed from histogram bins */ - histogram_quantiles: Array; + histogram_quantiles?: Array; /** * Counts of distinct values in column */ - freqtable_counts: Array; + freqtable_counts?: Array; /** * Number of other values not accounted for in counts */ - freqtable_other_count: number; + freqtable_other_count?: number; } @@ -111,12 +111,12 @@ export interface FreqtableCounts { /** * Stringified value */ - value: string; + value?: string; /** * Number of occurrences of value */ - count: number; + count?: number; } @@ -127,12 +127,12 @@ export interface BackendState { /** * The set of currently applied filters */ - filters: Array; + filters?: Array; /** * The set of currently applied sorts */ - sort_keys: Array; + sort_keys?: Array; } @@ -153,32 +153,32 @@ export interface ColumnSchema { /** * Column annotation / description */ - description: string; + description?: string; /** * Schema of nested child types */ - children: Array; + children?: Array; /** * Precision for decimal types */ - precision: number; + precision?: number; /** * Scale for decimal types */ - scale: number; + scale?: number; /** * Time zone for timestamp with time zone */ - timezone: string; + timezone?: string; /** * Size parameter for fixed-size types (list, binary) */ - type_size: number; + type_size?: number; } @@ -199,37 +199,37 @@ export interface ColumnFilter { /** * String representation of a binary comparison */ - compare_op: ColumnFilterCompareOp; + compare_op?: ColumnFilterCompareOp; /** * A stringified column value for a comparison filter */ - compare_value: string; + compare_value?: string; /** * Array of column values for a set membership filter */ - set_member_values: Array; + set_member_values?: Array; /** * Filter by including only values passed (true) or excluding (false) */ - set_member_inclusive: boolean; + set_member_inclusive?: boolean; /** * Type of search to perform */ - search_type: ColumnFilterSearchType; + search_type?: ColumnFilterSearchType; /** * String value/regex to search for in stringified data */ - search_term: string; + search_term?: string; /** * If true, do a case-sensitive search, otherwise case-insensitive */ - search_case_sensitive: boolean; + search_case_sensitive?: boolean; } @@ -240,18 +240,18 @@ export interface ColumnQuantileValue { /** * Quantile number (percentile). E.g. 1 for 1%, 50 for median */ - q: number; + q?: number; /** * Stringified quantile value */ - value: string; + value?: string; /** * Whether value is exact or approximate (computed from binned data or * sketches) */ - exact: boolean; + exact?: boolean; } @@ -262,12 +262,12 @@ export interface ColumnSortKey { /** * Column name to sort by */ - column: string; + column?: string; /** * Sort order, ascending (true) or descending (false) */ - ascending: boolean; + ascending?: boolean; } From bc554423267f67f3d242bc1933ffd62ed023089c Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 14:22:16 -0800 Subject: [PATCH 42/71] use Option for non-required members in Rust --- positron/comms/generate-comms.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index f1d2ba93201..dcc505d4393 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -353,12 +353,26 @@ use serde::Serialize; const name = o.name ? o.name : context[0] === 'items' ? context[1] : context[0]; yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; - for (const key of Object.keys(o.properties)) { + const props = Object.keys(o.properties); + for (let i = 0; i < props.length; i++) { + const key = props[i]; const prop = o.properties[key]; if (prop.description) { yield formatComment('\t/// ', prop.description); } - yield `\tpub ${key}: ${deriveType(source, RustTypeMap, key, prop)},\n\n`; + yield `\tpub ${key}: `; + if (!o.required || !o.required.includes(key)) { + yield 'Option<'; + yield deriveType(source, RustTypeMap, key, prop); + yield '>'; + + } else { + yield deriveType(source, RustTypeMap, key, prop); + } + if (i < props.length - 1) { + yield ',\n'; + } + yield '\n'; } yield '}\n\n'; }); From c5be0a9b12ecc64343c6ff174d50dd5883cf02e8 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 14:37:41 -0800 Subject: [PATCH 43/71] use Optional for non-required types on Python dataclasses --- positron/comms/generate-comms.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index dcc505d4393..449b851251e 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -560,10 +560,20 @@ from dataclasses import dataclass, field // Fields for (const prop of Object.keys(o.properties)) { const schema = o.properties[prop]; - yield ` ${prop}: ${deriveType(source, PythonTypeMap, prop, schema)}`; + yield ` ${prop}: `; + if (!o.required || !o.required.includes(prop)) { + yield 'Optional['; + yield deriveType(source, PythonTypeMap, prop, schema); + yield ']'; + } else { + yield deriveType(source, PythonTypeMap, prop, schema); + } yield ' = field(\n'; yield ` metadata={\n`; yield ` "description": "${schema.description}",\n`; + if (!o.required || !o.required.includes(prop)) { + yield ` "default": None,\n`; + } yield ` }\n`; yield ` )\n\n`; } From 5e7205f3544cde2221bb89b445d3792586fbf64d Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 15:14:41 -0800 Subject: [PATCH 44/71] add 'view' method for variables --- positron/comms/variables-backend-openrpc.json | 18 ++++++++++++++++++ .../common/positronVariablesComm.ts | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/positron/comms/variables-backend-openrpc.json b/positron/comms/variables-backend-openrpc.json index 0c3a9993d37..3a52a09a3b9 100644 --- a/positron/comms/variables-backend-openrpc.json +++ b/positron/comms/variables-backend-openrpc.json @@ -123,6 +123,24 @@ ] } } + }, + { + "name": "view", + "summary": "Request a viewer for a variable", + "description": "Request that the runtime open a data viewer to display the data in a variable.", + "params": [ + { + "name": "path", + "description": "The path to the variable to view, as an array of access keys.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "result": {} } ], "components": { diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index 9d6b6eba449..f4e37cd01a7 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -181,5 +181,19 @@ export class PositronVariablesComm extends PositronBaseComm { return super.performRpc('clipboard_format', ['path', 'format'], [path, format]); } + /** + * Request a viewer for a variable + * + * Request that the runtime open a data viewer to display the data in a + * variable. + * + * @param path The path to the variable to view, as an array of access + * keys. + * + */ + view(path: Array): Promise { + return super.performRpc('view', ['path'], [path]); + } + } From afc06746540c9d837f25d18e227054b1de62949a Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Thu, 21 Dec 2023 13:45:35 -0500 Subject: [PATCH 45/71] A bunch of work on using react-window - very messy --- .../actionBarComponents/layoutMenuButton.tsx | 12 +-- .../dataToolComponents/columnController.css | 5 +- .../dataToolComponents/columnController.tsx | 8 +- .../dataToolComponents/columnsPanel.css | 5 +- .../dataToolComponents/columnsPanel.tsx | 95 ++++++++++++++----- .../dataToolComponents/rowsPanel.tsx | 18 +--- .../browser/components/dataToolPanel.tsx | 10 +- .../browser/positronDataToolEditor.tsx | 83 ++++++++-------- .../interfaces/positronDataToolService.ts | 16 ++-- .../browser/positronDataToolService.ts | 60 ++++++------ 10 files changed, 180 insertions(+), 132 deletions(-) diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx index 7c0e3323554..d0054131f8a 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/actionBarComponents/layoutMenuButton.tsx @@ -25,12 +25,12 @@ const columnsHidden = localize('positron.columnsHidden', "Columns Hidden"); */ export const LayoutMenuButton = () => { // Context hooks. - const positronDataToolContext = usePositronDataToolContext(); + const context = usePositronDataToolContext(); // Builds the actions. const actions = () => { // Get the current layout. - const layout = positronDataToolContext.instance.layout; + const layout = context.instance.layout; // Build the actions. const actions: IAction[] = []; @@ -44,7 +44,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsLeft, run: () => { - positronDataToolContext.instance.layout = PositronDataToolLayout.ColumnsLeft; + context.instance.layout = PositronDataToolLayout.ColumnsLeft; } }); @@ -57,7 +57,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsRight, run: () => { - positronDataToolContext.instance.layout = PositronDataToolLayout.ColumnsRight; + context.instance.layout = PositronDataToolLayout.ColumnsRight; } }); @@ -73,7 +73,7 @@ export const LayoutMenuButton = () => { enabled: true, checked: layout === PositronDataToolLayout.ColumnsHidden, run: () => { - positronDataToolContext.instance.layout = PositronDataToolLayout.ColumnsHidden; + context.instance.layout = PositronDataToolLayout.ColumnsHidden; } }); @@ -86,7 +86,7 @@ export const LayoutMenuButton = () => { * @returns The icon ID for the layout. */ const selectIconId = () => { - switch (positronDataToolContext.instance.layout) { + switch (context.instance.layout) { // Columns left. case PositronDataToolLayout.ColumnsLeft: return 'positron-data-tool-columns-left'; diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController.css b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController.css index 4081b658953..55cffae99b3 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController.css +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController.css @@ -3,5 +3,8 @@ *--------------------------------------------------------------------------------------------*/ .data-tool-panel .columns-panel .column-controller { - + display: flex; + box-sizing: border-box; /* Draw the borders inside the box. */ + align-items: center; + height: 26px; } diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController.tsx index 9134a3fc7cb..a8b218ace2c 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController.tsx @@ -4,11 +4,15 @@ import 'vs/css!./columnController'; import * as React from 'react'; +import { CSSProperties } from 'react'; // eslint-disable-line no-duplicate-imports +import { DummyColumnInfo } from 'vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel'; /** * ColumnControllerProps interface. */ interface ColumnControllerProps { + dummyColumnInfo: DummyColumnInfo; + style: CSSProperties; } /** @@ -18,8 +22,8 @@ interface ColumnControllerProps { */ export const ColumnController = (props: ColumnControllerProps) => { return ( -
-
Name
+
+ {props.dummyColumnInfo.name}
); }; diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.css b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.css index 0e96b59ea12..196db9abc48 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.css +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.css @@ -4,9 +4,10 @@ .data-tool-panel .columns-panel { /* This is the scroll container. */ - overflow-y: scroll; + /* overflow-y: scroll; */ } .data-tool-panel .columns-panel .title { - padding: 10px; + /* display: flex; */ + height: 26px; } diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.tsx index 4ba796fd708..292dac1f498 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnsPanel.tsx @@ -4,20 +4,28 @@ import 'vs/css!./columnsPanel'; import * as React from 'react'; -import { UIEvent, useEffect, useRef, useState } from 'react'; // eslint-disable-line no-duplicate-imports +import { useEffect, useRef, useState } from 'react'; // eslint-disable-line no-duplicate-imports import { generateUuid } from 'vs/base/common/uuid'; +import { FixedSizeList as List, ListChildComponentProps, ListOnItemsRenderedProps, ListOnScrollProps } from 'react-window'; import { usePositronDataToolContext } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolContext'; +import { ColumnController } from 'vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/columnController'; + +/** + * Constants. + */ +const LINE_HEIGHT = 26; /** * ColumnsPanelProps interface. */ interface ColumnsPanelProps { + height: number; } /** * DummyColumnInfo interface. */ -interface DummyColumnInfo { +export interface DummyColumnInfo { key: string; name: string; } @@ -30,7 +38,7 @@ const dummyColumns: DummyColumnInfo[] = []; /** * Fill the dummy columns. */ -for (let i = 0; i < 100; i++) { +for (let i = 0; i < 64; i++) { dummyColumns.push({ key: generateUuid(), name: `This is column ${i + 1}` @@ -48,37 +56,80 @@ export const ColumnsPanel = (props: ColumnsPanelProps) => { // Reference hooks. const columnsPanel = useRef(undefined!); + const listRef = useRef(undefined!); + const innerRef = useRef(undefined!); - const [columnsScrollPosition, setcolumnsScrollPosition] = useState(undefined); + // State hooks. + const [firstRender, setFirstRender] = useState(true); useEffect(() => { - setcolumnsScrollPosition(context.instance.columnsScrollPosition); - }, []); - - useEffect(() => { - if (columnsPanel.current && columnsScrollPosition) { - setTimeout(() => { - columnsPanel.current.scrollBy(0, columnsScrollPosition); - }, 100); + if (!firstRender) { + listRef.current.scrollTo(20 * LINE_HEIGHT); } + }, [firstRender]); + + const itemsRenderedHandler = ({ + visibleStartIndex, + visibleStopIndex + }: ListOnItemsRenderedProps) => { + console.log(context); + setFirstRender(true); + console.log(`-----------------> LIST height ${props.height} itemsRenderedHandler: visibleStartIndex ${visibleStartIndex} visibleStopIndex ${visibleStopIndex}`); + }; - }, [columnsPanel, columnsScrollPosition]); + const scrollHandler = ({ + scrollDirection, + scrollOffset, + }: ListOnScrollProps) => { + console.log(`-----------------> LIST height ${props.height} scrollHandler: scrollDirection ${scrollDirection} scrollOffset ${scrollOffset}`); + // context.instance.columnsScrollOffset = props.scrollOffset; + + }; /** - * onScroll event handler. - * @param e A UIEvent that describes a user interaction with the mouse. + * ColumnEntry component. + * @param index The index of the column entry. + * @param style The style (positioning) at which to render the column entry. + * @param isScrolling A value which indicates whether the list is scrolling. + * @returns The rendered column entry. */ - const scrollHandler = (e: UIEvent) => { - // Set the scroll position. - context.instance.columnsScrollPosition = columnsPanel.current.scrollTop; + const ColumnEntry = (props: ListChildComponentProps) => { + // Get the entry being rendered. + const column = dummyColumns[props.index]; + + console.log(`Render ColumnEntry ${props.index}`); + + if (!firstRender) { + return ( +
+ ); + } + + // Render. + return ( + + ); }; // Render. return ( -
- {dummyColumns.map(column => -
{column.name}
- )} +
+ dummyColumns[index].key} + width='100%' + height={props.height - 2} + itemSize={LINE_HEIGHT} + overscanCount={10} + onItemsRendered={itemsRenderedHandler} + onScroll={scrollHandler} + > + {ColumnEntry} +
); }; diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.tsx index 43f7be08312..1b9e370bde1 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolComponents/rowsPanel.tsx @@ -6,7 +6,7 @@ import 'vs/css!./rowsPanel'; import * as React from 'react'; import { UIEvent, useEffect, useRef } from 'react'; // eslint-disable-line no-duplicate-imports import { generateUuid } from 'vs/base/common/uuid'; -import { usePositronDataToolContext } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolContext'; +// import { usePositronDataToolContext } from 'vs/workbench/contrib/positronDataTool/browser/positronDataToolContext'; /** * RowsPanelProps interface. @@ -44,14 +44,14 @@ for (let i = 0; i < 100; i++) { */ export const RowsPanel = (props: RowsPanelProps) => { // Context hooks. - const context = usePositronDataToolContext(); + // const context = usePositronDataToolContext(); // Reference hooks. const rowsPanel = useRef(undefined!); // Main useEffect. useEffect(() => { - // console.log(`rowsPanel is ${rowsPanel} and rowsScrollPosition is ${context.instance.rowsScrollPosition}`); + // console.log(`rowsPanel is ${rowsPanel} and rowsScrollPosition is ${context.instance.rowsScrollOffset}`); }, []); /** @@ -59,18 +59,6 @@ export const RowsPanel = (props: RowsPanelProps) => { * @param e A UIEvent that describes a user interaction with the mouse. */ const scrollHandler = (e: UIEvent) => { - // // Calculate the scroll position. - // const scrollPosition = Math.abs( - // rowsPanel.current.scrollHeight - - // rowsPanel.current.clientHeight - - // rowsPanel.current.scrollTop - // ); - - // Set the scroll position. - // context.instance.rowsScrollPosition = scrollPosition; - - // Log. - // console.log(`Scroll position ${scrollPosition}`); }; // Render. diff --git a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx index af91094fca1..7d724a6c1f7 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/components/dataToolPanel.tsx @@ -117,15 +117,11 @@ export const DataToolPanel = (props: DataToolPanelProps) => { column2.current.style.gridRow = 'main / end'; column2.current.style.gridColumn = 'column / end'; - column2.current.style.display = 'inline'; + column2.current.style.display = 'grid'; break; } }, [layout, columnsWidth]); - // Width effect. - useEffect(() => { - }, [props.width]); - /** * onResize handler. * @param x The X delta. @@ -146,7 +142,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { // Columns hidden. This can't happen. case PositronDataToolLayout.ColumnsHidden: - return PositronColumnSplitterResizeResult.TooLarge; + throw new Error('Resize is unavailable'); } // If the new column width is too small, pin it at the minimum column width and return @@ -182,7 +178,7 @@ export const DataToolPanel = (props: DataToolPanelProps) => { className='data-tool-panel' >
- +
diff --git a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx index e59c19a785f..00676de63b9 100644 --- a/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx +++ b/src/vs/workbench/contrib/positronDataTool/browser/positronDataToolEditor.tsx @@ -95,7 +95,9 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen /** * Gets the instance. This is a temporary property. */ - private instance = `${++instance}`; + private _instance = `${++instance}`; + + private _identifier?: string; //#endregion Private Properties @@ -193,7 +195,7 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen super(PositronDataToolEditorInput.EditorID, telemetryService, themeService, storageService); // Logging. - console.log(`PositronDataEditor ${this.instance} created.`); + console.log(`PositronDataEditor ${this._instance} created`); } /** @@ -201,7 +203,7 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen */ public override dispose(): void { // Logging. - console.log(`PositronDataEditor ${this.instance} dispose`); + console.log(`PositronDataEditor ${this._instance} dispose`); // Dispose the PositronReactRenderer for the PositronDataTool. this.disposePositronReactRenderer(); @@ -220,7 +222,7 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen */ protected override createEditor(parent: HTMLElement): void { // Logging. - console.log(`PositronDataEditor ${this.instance} createEditor`); + console.log(`PositronDataEditor ${this._instance} createEditor`); // Create and append the Positron data tool container. this._positronDataToolsContainer = DOM.$('.positron-data-tool-container'); @@ -244,39 +246,10 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen await super.setInput(input, options, context, token); // Logging. - console.log(`PositronDataEditor ${this.instance} setInput ${input.resource}`); + console.log(`PositronDataEditor ${this._instance} setInput ${input.resource}`); // Parse the Positron data tool URI. - const identifier = PositronDataToolUri.parse(input.resource); - if (identifier) { - // Get the Positron data tool instance. - const positronDataToolInstance = this._positronDataToolService.getInstance(identifier); - - // If the Positron data tool instance was found, render the Positron data tool. - if (positronDataToolInstance) { - // Create the PositronReactRenderer for the PositronDataTool component and render it. - this._positronReactRenderer = new PositronReactRenderer(this._positronDataToolsContainer); - this._positronReactRenderer.render( - - ); - - // Logging. - console.log(`PositronDataEditor ${this.instance} create PositronReactRenderer`); - - - // Success. - return; - } - } + this._identifier = PositronDataToolUri.parse(input.resource); // TODO: Render some kind of an error. } @@ -286,11 +259,13 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen */ override clearInput(): void { // Logging. - console.log(`PositronDataEditor ${this.instance} clearInput`); + console.log(`PositronDataEditor ${this._instance} clearInput`); // Dispose the PositronReactRenderer for the PositronDataTool. this.disposePositronReactRenderer(); + this._identifier = undefined; + // Call the base class's method. super.clearInput(); } @@ -302,7 +277,7 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen */ protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { // Logging. - console.log(`PositronDataEditor ${this.instance} setEditorVisible ${visible} group ${group}`); + console.log(`PositronDataEditor ${this._instance} setEditorVisible ${visible} group ${group?.id}`); // Call the base class's method. super.setEditorVisible(visible, group); @@ -318,7 +293,7 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen */ override layout(dimension: DOM.Dimension): void { // Logging. - console.log(`PositronDataEditor ${this.instance} layout ${dimension.width},${dimension.height}`); + console.log(`PositronDataEditor ${this._instance} layout ${dimension.width},${dimension.height}`); // Size the container. DOM.size(this._positronDataToolsContainer, dimension.width, dimension.height); @@ -330,6 +305,36 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen width: this._width, height: this._height }); + + if (this._identifier && !this._positronReactRenderer) { + // Get the Positron data tool instance. + const positronDataToolInstance = this._positronDataToolService.getInstance(this._identifier); + + // If the Positron data tool instance was found, render the Positron data tool. + if (positronDataToolInstance) { + // Create the PositronReactRenderer for the PositronDataTool component and render it. + this._positronReactRenderer = new PositronReactRenderer(this._positronDataToolsContainer); + this._positronReactRenderer.render( + + ); + + // Logging. + console.log(`PositronDataEditor ${this._instance} create PositronReactRenderer`); + + + // Success. + return; + } + } } //#endregion Protected Overrides @@ -344,7 +349,7 @@ export class PositronDataToolEditor extends EditorPane implements IReactComponen // the PositronDataTool from the DOM. if (this._positronReactRenderer) { // Logging. - console.log(`PositronDataEditor ${this.instance} dispose PositronReactRenderer`); + console.log(`PositronDataEditor ${this._instance} dispose PositronReactRenderer`); // Dispose of the PositronReactRenderer for the PositronDataTool. this._positronReactRenderer.dispose(); diff --git a/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts index a1ddadeb7b1..7fff75f3908 100644 --- a/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts +++ b/src/vs/workbench/services/positronDataTool/browser/interfaces/positronDataToolService.ts @@ -59,14 +59,14 @@ export interface IPositronDataToolInstance { columnsWidthPercent: number; /** - * Gets or sets the columns scroll position. + * Gets or sets the columns scroll offset. */ - columnsScrollPosition: number; + columnsScrollOffset: number; /** - * Gets or sets the rows scroll position. + * Gets or sets the rows scroll offset. */ - rowsScrollPosition: number; + rowsScrollOffset: number; /** * The onDidChangeLayout event. @@ -79,12 +79,12 @@ export interface IPositronDataToolInstance { readonly onDidChangeColumnsWidthPercent: Event; /** - * The onDidChangeColumnsScrollPosition event. + * The onDidChangeColumnsScrollOffset event. */ - readonly onDidChangeColumnsScrollPosition: Event; + readonly onDidChangeColumnsScrollOffset: Event; /** - * The onDidChangeRowsScrollPosition event. + * The onDidChangeRowsScrollOffset event. */ - readonly onDidChangeRowsScrollPosition: Event; + readonly onDidChangeRowsScrollOffset: Event; } diff --git a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts index ad7603c6e45..1b31345fcbe 100644 --- a/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts +++ b/src/vs/workbench/services/positronDataTool/browser/positronDataToolService.ts @@ -88,20 +88,19 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn private _columnsWidthPercent = 0.25; /** - * Gets or sets the columns scroll position. + * Gets or sets the columns scroll offset. */ - private _columnsScrollPosition = 0; + private _columnsScrollOffset = 200; /** - * Gets or sets the rows scroll position. + * Gets or sets the rows scroll offset. */ - private _rowsScrollPosition = 0; + private _rowsScrollOffset = 0; /** * The onDidChangeLayout event emitter. */ - private readonly _onDidChangeLayoutEmitter = - this._register(new Emitter); + private readonly _onDidChangeLayoutEmitter = this._register(new Emitter); /** * The onDidChangeColumnsWidthPercent event emitter. @@ -109,14 +108,14 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn private readonly _onDidChangeColumnsWidthPercentEmitter = this._register(new Emitter); /** - * The onDidChangeColumnsScrollPositon event emitter. + * The onDidChangeColumnsScrollOffset event emitter. */ - private readonly _onDidChangeColumnsScrollPositionEmitter = this._register(new Emitter); + private readonly _onDidChangeColumnsScrollOffsetEmitter = this._register(new Emitter); /** - * The onDidChangeRowsScrollPositon event emitter. + * The onDidChangeRowsScrollOffset event emitter. */ - private readonly _onDidChangeRowsScrollPositionEmitter = this._register(new Emitter); + private readonly _onDidChangeRowsScrollOffsetEmitter = this._register(new Emitter); //#endregion Private Properties @@ -170,36 +169,37 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn } /** - * Gets the columns scroll position. + * Gets the columns scroll offset. */ - get columnsScrollPosition() { - return this._columnsScrollPosition; + get columnsScrollOffset() { + return this._columnsScrollOffset; } /** - * Sets the columns scroll position. + * Sets the columns scroll offset. */ - set columnsScrollPosition(columnsScrollPosition: number) { - if (columnsScrollPosition !== this._columnsScrollPosition) { - this._columnsScrollPosition = columnsScrollPosition; - this._onDidChangeColumnsScrollPositionEmitter.fire(this._columnsScrollPosition); + set columnsScrollOffset(columnsScrollOffset: number) { + console.log(`************************* setting column scroll offset to ${columnsScrollOffset}`); + if (columnsScrollOffset !== this._columnsScrollOffset) { + this._columnsScrollOffset = columnsScrollOffset; + this._onDidChangeColumnsScrollOffsetEmitter.fire(this._columnsScrollOffset); } } /** - * Gets the rows scroll position. + * Gets the rows scroll offset. */ - get rowsScrollPosition() { - return this._rowsScrollPosition; + get rowsScrollOffset() { + return this._rowsScrollOffset; } /** - * Sets the rows scroll position. + * Sets the rows scroll offset. */ - set rowsScrollPosition(rowsScrollPosition: number) { - if (rowsScrollPosition !== this._rowsScrollPosition) { - this._rowsScrollPosition = rowsScrollPosition; - this._onDidChangeRowsScrollPositionEmitter.fire(this._rowsScrollPosition); + set rowsScrollOffset(rowsScrollOffset: number) { + if (rowsScrollOffset !== this._rowsScrollOffset) { + this._rowsScrollOffset = rowsScrollOffset; + this._onDidChangeRowsScrollOffsetEmitter.fire(this._rowsScrollOffset); } } @@ -214,14 +214,14 @@ class PositronDataToolInstance extends Disposable implements IPositronDataToolIn readonly onDidChangeColumnsWidthPercent = this._onDidChangeColumnsWidthPercentEmitter.event; /** - * onDidChangeColumnsScrollPosition event. + * onDidChangeColumnsScrollOffset event. */ - readonly onDidChangeColumnsScrollPosition = this._onDidChangeColumnsScrollPositionEmitter.event; + readonly onDidChangeColumnsScrollOffset = this._onDidChangeColumnsScrollOffsetEmitter.event; /** - * onDidChangeRowsScrollPosition event. + * onDidChangeRowsScrollOffset event. */ - readonly onDidChangeRowsScrollPosition = this._onDidChangeRowsScrollPositionEmitter.event; + readonly onDidChangeRowsScrollOffset = this._onDidChangeRowsScrollOffsetEmitter.event; //#endregion IPositronDataToolInstance Implementation } From a57fe17ee54c0833c2f13421ef26a195066e96b4 Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 26 Dec 2023 16:23:55 +0200 Subject: [PATCH 46/71] use `DOM.getActiveWindow()` --- .../positronConsole/browser/components/consoleInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx b/src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx index 0821fdf9cd2..87cb05043bd 100644 --- a/src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx +++ b/src/vs/workbench/contrib/positronConsole/browser/components/consoleInput.tsx @@ -3,6 +3,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./consoleInput'; +import * as DOM from 'vs/base/browser/dom'; import * as React from 'react'; import { FocusEvent, useEffect, useRef } from 'react'; // eslint-disable-line no-duplicate-imports import { URI } from 'vs/base/common/uri'; @@ -163,8 +164,7 @@ export const ConsoleInput = (props: ConsoleInputProps) => { // 'scoped' contexts makes that challenging to access here, and I // haven't figured out the 'right' way to get access to those contexts. if (!cmdOrCtrlKey) { - // eslint-disable-next-line no-restricted-syntax - const suggestWidgets = document.getElementsByClassName('suggest-widget'); + const suggestWidgets = DOM.getActiveWindow().document.getElementsByClassName('suggest-widget'); for (const suggestWidget of suggestWidgets) { if (suggestWidget.classList.contains('visible')) { return; From d4ef43ae43e5c1169666f016c40751cdf77d4fe3 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 15:26:27 -0800 Subject: [PATCH 47/71] don't import Event without front-end events --- positron/comms/generate-comms.ts | 8 ++++++-- .../languageRuntime/common/positronDataToolComm.ts | 1 - .../languageRuntime/common/positronVariablesComm.ts | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 449b851251e..def1273018c 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -793,8 +793,12 @@ function* createTypescriptComm(name: string, frontend: any, backend: any): Gener // AUTO-GENERATED from ${name}.json; do not edit. // -import { Event } from 'vs/base/common/event'; -import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; +`; + // If there are frontend events, import the Event class + if (frontend) { + yield `import { Event } from 'vs/base/common/event';\n`; + } + yield `import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; `; diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index 5429b97a1b7..f12006979f8 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -6,7 +6,6 @@ // AUTO-GENERATED from data_tool.json; do not edit. // -import { Event } from 'vs/base/common/event'; import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index f4e37cd01a7..75d0c0e83d0 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -6,7 +6,6 @@ // AUTO-GENERATED from variables.json; do not edit. // -import { Event } from 'vs/base/common/event'; import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; From 65c97fc84f00afda577aecb77ec211d6bef20c93 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 20 Dec 2023 16:09:17 -0800 Subject: [PATCH 48/71] allow "mildly illegal" cross contract refs for shared frontend/backend types --- positron/comms/generate-comms.ts | 81 ++++++++++++------- .../comms/variables-frontend-openrpc.json | 36 +++++++++ .../common/positronVariablesComm.ts | 25 ++++++ 3 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 positron/comms/variables-frontend-openrpc.json diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index def1273018c..389d79ec681 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -104,10 +104,11 @@ function snakeCaseToSentenceCase(name: string) { * Parse a ref tag to get the name of the object referred to by the ref. * * @param ref The ref to parse + * @param contract The OpenRPC contract that the ref is part of * @returns The name of the object referred to by the ref, as a SentenceCase - * string + * string, or undefined if the ref could not be parsed or found. */ -function parseRef(ref: string, contract: any): string { +function parseRefFromContract(ref: string, contract: any): string | undefined { // Split the ref into parts, and then walk the contract to find the // referenced object const parts = ref.split('/'); @@ -119,33 +120,55 @@ function parseRef(ref: string, contract: any): string { if (Object.keys(target).includes(parts[i])) { target = target[parts[i]]; } else { - throw new Error(`Invalid ref: ${ref} (part '${parts[i]}' not found)`); + return undefined; } } return snakeCaseToSentenceCase(parts[parts.length - 1]); } +/** + * Parse a ref tag to get the name of the object referred to by the ref. + * Searches all the given contracts for the ref; throws if the ref cannot be + * found in any of the contracts. + * + * @param ref The ref to parse + * @param contracts The OpenRPC contracts to search for the ref. + * @returns The name of the object referred to by the ref. + */ +function parseRef(ref: string, contracts: Array): string { + for (const contract of contracts) { + if (!contract) { + continue; + } + const name = parseRefFromContract(ref, contract); + if (name) { + return name; + } + } + throw new Error(`Could not find ref: ${ref}`); +} + /** * Generic function for deriving a type from a schema. * - * @param contract The OpenRPC contract that the schema is part of + * @param contract The OpenRPC contracts * @param typeMap A map from schema types to language types * @param key The key beneath which this schema is defined * @param schema The schema to derive a type from * * @returns A string containing the derived type */ -function deriveType(contract: any, +function deriveType(contracts: Array, typeMap: Record, key: string, schema: any): string { if (schema.type === 'array') { // If the array has a ref, use that to derive an array type return typeMap['array-begin'] + - deriveType(contract, typeMap, key, schema.items) + + deriveType(contracts, typeMap, key, schema.items) + typeMap['array-end']; } else if (schema.$ref) { - return parseRef(schema.$ref, contract); + return parseRef(schema.$ref, contracts); } else if (schema.type === 'object') { if (schema.name) { return snakeCaseToSentenceCase(schema.name); @@ -321,7 +344,9 @@ use serde::Serialize; `; - for (const source of [backend, frontend]) { + const contracts = [backend, frontend]; + + for (const source of contracts) { if (!source) { continue; } @@ -333,7 +358,7 @@ use serde::Serialize; if (schema.type !== 'object') { yield formatComment('/// ', schema.description); yield `type ${snakeCaseToSentenceCase(key)} = `; - yield deriveType(source, RustTypeMap, + yield deriveType(contracts, RustTypeMap, schema.name ? schema.name : key, schema); yield ';\n\n'; @@ -363,11 +388,11 @@ use serde::Serialize; yield `\tpub ${key}: `; if (!o.required || !o.required.includes(key)) { yield 'Option<'; - yield deriveType(source, RustTypeMap, key, prop); + yield deriveType(contracts, RustTypeMap, key, prop); yield '>'; } else { - yield deriveType(source, RustTypeMap, key, prop); + yield deriveType(contracts, RustTypeMap, key, prop); } if (i < props.length - 1) { yield ',\n'; @@ -423,7 +448,7 @@ use serde::Serialize; yield `\tpub ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)},\n`; } else { // Otherwise use the type directly - yield `\tpub ${param.name}: ${deriveType(source, RustTypeMap, param.name, param.schema)},\n`; + yield `\tpub ${param.name}: ${deriveType(contracts, RustTypeMap, param.name, param.schema)},\n`; } if (i < method.params.length - 1) { yield '\n'; @@ -528,8 +553,8 @@ import enum from dataclasses import dataclass, field `; - - for (const source of [backend, frontend]) { + const contracts = [backend, frontend]; + for (const source of contracts) { if (!source) { continue; } @@ -563,10 +588,10 @@ from dataclasses import dataclass, field yield ` ${prop}: `; if (!o.required || !o.required.includes(prop)) { yield 'Optional['; - yield deriveType(source, PythonTypeMap, prop, schema); + yield deriveType(contracts, PythonTypeMap, prop, schema); yield ']'; } else { - yield deriveType(source, PythonTypeMap, prop, schema); + yield deriveType(contracts, PythonTypeMap, prop, schema); } yield ' = field(\n'; yield ` metadata={\n`; @@ -637,7 +662,7 @@ from dataclasses import dataclass, field if (param.schema.enum) { yield ` ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield ` ${param.name}: ${deriveType(backend, PythonTypeMap, param.name, param.schema)}`; + yield ` ${param.name}: ${deriveType(contracts, PythonTypeMap, param.name, param.schema)}`; } yield ' = field(\n'; yield ` metadata={\n`; @@ -708,7 +733,7 @@ from dataclasses import dataclass, field if (param.schema.enum) { yield ` ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield ` ${param.name}: ${deriveType(backend, PythonTypeMap, param.name, param.schema)}`; + yield ` ${param.name}: ${deriveType(contracts, PythonTypeMap, param.name, param.schema)}`; } yield ' = field(\n'; yield ` metadata={\n`; @@ -724,7 +749,7 @@ from dataclasses import dataclass, field /** * Generates a Typescript interface for a given object schema. * - * @param contract The OpenRPC contract that the schema is part of + * @param contract The OpenRPC contracts that the schema is part of * @param name The name of the schema * @param description The description of the schema * @param properties The properties of the schema @@ -733,7 +758,7 @@ from dataclasses import dataclass, field * @returns A generator that yields the Typescript code for an interface * representing the schema */ -function* createTypescriptInterface(contract: any, +function* createTypescriptInterface(contracts: Array, name: string, description: string, properties: Record, @@ -767,7 +792,7 @@ function* createTypescriptInterface(contract: any, } else if (schema.type === 'string' && schema.enum) { yield `${snakeCaseToSentenceCase(name)}${snakeCaseToSentenceCase(prop)}`; } else { - yield deriveType(contract, TypescriptTypeMap, prop, schema); + yield deriveType(contracts, TypescriptTypeMap, prop, schema); } yield `;\n\n`; } @@ -802,8 +827,8 @@ function* createTypescriptComm(name: string, frontend: any, backend: any): Gener import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; `; - - for (const source of [backend, frontend]) { + const contracts = [backend, frontend]; + for (const source of contracts) { if (!source) { continue; } @@ -813,7 +838,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co const description = o.description ? o.description : snakeCaseToSentenceCase(context[0]) + ' in ' + snakeCaseToSentenceCase(context[1]); - yield* createTypescriptInterface(source, name, description, o.properties, + yield* createTypescriptInterface(contracts, name, description, o.properties, o.required ? o.required : []); }); @@ -851,7 +876,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co yield formatComment(' * ', schema.description); yield ' */\n'; yield `export type ${snakeCaseToSentenceCase(key)} = `; - yield deriveType(source, TypescriptTypeMap, key, schema); + yield deriveType(contracts, TypescriptTypeMap, key, schema); yield ';\n\n'; } } @@ -876,7 +901,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (param.schema.type === 'string' && param.schema.enum) { yield `${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield deriveType(frontend, TypescriptTypeMap, param.name, param.schema); + yield deriveType(contracts, TypescriptTypeMap, param.name, param.schema); } yield `;\n\n`; } @@ -943,7 +968,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (schema.type === 'string' && schema.enum) { yield `${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { - yield deriveType(backend, TypescriptTypeMap, param.name, schema); + yield deriveType(contracts, TypescriptTypeMap, param.name, schema); } if (i < method.params.length - 1) { yield ', '; @@ -954,7 +979,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co if (method.result.schema.type === 'object') { yield snakeCaseToSentenceCase(method.result.schema.name); } else { - yield deriveType(backend, TypescriptTypeMap, method.name, method.result.schema); + yield deriveType(contracts, TypescriptTypeMap, method.name, method.result.schema); } } else { yield 'void'; diff --git a/positron/comms/variables-frontend-openrpc.json b/positron/comms/variables-frontend-openrpc.json new file mode 100644 index 00000000000..4492bc5a246 --- /dev/null +++ b/positron/comms/variables-frontend-openrpc.json @@ -0,0 +1,36 @@ +{ + "openrpc": "1.3.0", + "info": { + "title": "Variables Frontend", + "version": "1.0.0" + }, + "methods": [ + { + "name": "update", + "summary": "Update variables", + "description": "Updates the variables in the current session.", + "params": [ + { + "name": "assigned", + "description": "An array of variables that have been newly assigned.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/variable" + } + } + }, + { + "name": "removed", + "description": "An array of variable names that have been removed.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ] + } + ] +} diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index 75d0c0e83d0..2a4632ac970 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -6,6 +6,7 @@ // AUTO-GENERATED from variables.json; do not edit. // +import { Event } from 'vs/base/common/event'; import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; @@ -120,9 +121,26 @@ export enum VariableKind { Table = 'table' } +/** + * Event: Update variables + */ +export interface UpdateEvent { + /** + * An array of variables that have been newly assigned. + */ + assigned: Array; + + /** + * An array of variable names that have been removed. + */ + removed: Array; + +} + export class PositronVariablesComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); + this.onDidUpdate = super.createEventEmitter('update', ['assigned', 'removed']); } /** @@ -194,5 +212,12 @@ export class PositronVariablesComm extends PositronBaseComm { return super.performRpc('view', ['path'], [path]); } + + /** + * Update variables + * + * Updates the variables in the current session. + */ + onDidUpdate: Event; } From 6460bca7a747a66dfb38d3ddeec3890426b9f696 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Thu, 21 Dec 2023 11:23:27 -0800 Subject: [PATCH 49/71] update ark to 0.1.39 --- extensions/positron-r/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-r/package.json b/extensions/positron-r/package.json index ec52c3b199e..499c399859f 100644 --- a/extensions/positron-r/package.json +++ b/extensions/positron-r/package.json @@ -503,7 +503,7 @@ }, "positron": { "binaryDependencies": { - "ark": "0.1.38" + "ark": "0.1.39" } } } From 9d7d3cf816e72a6397191b7756f92ec957be959e Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Mon, 18 Dec 2023 11:02:19 +0100 Subject: [PATCH 50/71] Specify frontend message interface --- positron/comms/frontend-backend-openrpc.json | 8 ++ positron/comms/frontend-frontend-openrpc.json | 72 +++++++++++++ positron/comms/frontend.json | 9 ++ .../common/positronFrontendComm.ts | 102 ++++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 positron/comms/frontend-backend-openrpc.json create mode 100644 positron/comms/frontend-frontend-openrpc.json create mode 100644 positron/comms/frontend.json create mode 100644 src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts diff --git a/positron/comms/frontend-backend-openrpc.json b/positron/comms/frontend-backend-openrpc.json new file mode 100644 index 00000000000..136d4feb1e2 --- /dev/null +++ b/positron/comms/frontend-backend-openrpc.json @@ -0,0 +1,8 @@ +{ + "openrpc": "1.3.0", + "info": { + "title": "Frontend Backend", + "version": "1.0.0" + }, + "methods": [] +} diff --git a/positron/comms/frontend-frontend-openrpc.json b/positron/comms/frontend-frontend-openrpc.json new file mode 100644 index 00000000000..2afa6442e2b --- /dev/null +++ b/positron/comms/frontend-frontend-openrpc.json @@ -0,0 +1,72 @@ +{ + "openrpc": "1.3.0", + "info": { + "title": "Frontend Frontend", + "version": "1.0.0" + }, + "methods": [ + { + "name": "busy", + "summary": "Change in backend's busy/idle status", + "description": "This represents the busy state of the underlying computation engine, not the busy state of the kernel. The kernel is busy when it is processing a request, but the runtime is busy only when a computation is running.", + "params": [ + { + "name": "busy", + "description": "Whether the backend is busy", + "schema": { + "type": "boolean" + } + } + ] + }, + { + "name": "show_message", + "summary": "Show a message", + "description": "Use this for messages that require immediate attention from the user", + "params": [ + { + "name": "message", + "description": "The message to show to the user.", + "schema": { + "type": "string" + } + } + ] + }, + { + "name": "prompt_state", + "summary": "New state of the primary and secondary prompts", + "description": "Languages like R allow users to change the way their prompts look. This event signals a change in the prompt configuration.", + "params": [ + { + "name": "input_prompt", + "description": "Prompt for primary input.", + "schema": { + "type": "string" + } + }, + { + "name": "continuation_prompt", + "description": "Prompt for incomplete input.", + "schema": { + "type": "string" + } + } + ] + }, + { + "name": "working_directory", + "summary": "Change the displayed working directory", + "description": "This event signals a change in the working direcotry of the interpreter", + "params": [ + { + "name": "directory", + "description": "The new working directory", + "schema": { + "type": "string" + } + } + ] + } + ] +} diff --git a/positron/comms/frontend.json b/positron/comms/frontend.json new file mode 100644 index 00000000000..46e2c26d460 --- /dev/null +++ b/positron/comms/frontend.json @@ -0,0 +1,9 @@ +{ + "name": "frontend", + "initiator": "frontend", + "initial_data": { + "schema": { + "type": "null" + } + } +} diff --git a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts new file mode 100644 index 00000000000..8df37a9c857 --- /dev/null +++ b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// +// AUTO-GENERATED from frontend.json; do not edit. +// + +import { Event } from 'vs/base/common/event'; +import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; +import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; + +/** + * Event: Change in backend's busy/idle status + */ +export interface BusyEvent { + /** + * Whether the backend is busy + */ + busy: boolean; + +} + +/** + * Event: Show a message + */ +export interface ShowMessageEvent { + /** + * The message to show to the user. + */ + message: string; + +} + +/** + * Event: New state of the primary and secondary prompts + */ +export interface PromptStateEvent { + /** + * Prompt for primary input. + */ + inputPrompt: string; + + /** + * Prompt for incomplete input. + */ + continuationPrompt: string; + +} + +/** + * Event: Change the displayed working directory + */ +export interface WorkingDirectoryEvent { + /** + * The new working directory + */ + directory: string; + +} + +export class PositronFrontendComm extends PositronBaseComm { + constructor(instance: IRuntimeClientInstance) { + super(instance); + this.onDidBusy = super.createEventEmitter('busy', ['busy']); + this.onDidShowMessage = super.createEventEmitter('show_message', ['message']); + this.onDidPromptState = super.createEventEmitter('prompt_state', ['input_prompt', 'continuation_prompt']); + this.onDidWorkingDirectory = super.createEventEmitter('working_directory', ['directory']); + } + + + /** + * Change in backend's busy/idle status + * + * This represents the busy state of the underlying computation engine, + * not the busy state of the kernel. The kernel is busy when it is + * processing a request, but the runtime is busy only when a computation + * is running. + */ + onDidBusy: Event; + /** + * Show a message + * + * Use this for messages that require immediate attention from the user + */ + onDidShowMessage: Event; + /** + * New state of the primary and secondary prompts + * + * Languages like R allow users to change the way their prompts look. + * This event signals a change in the prompt configuration. + */ + onDidPromptState: Event; + /** + * Change the displayed working directory + * + * This event signals a change in the working direcotry of the + * interpreter + */ + onDidWorkingDirectory: Event; +} + From fd7c296451d57c7d87b8ad7f5b2d07bbd2e69261 Mon Sep 17 00:00:00 2001 From: seem Date: Fri, 22 Dec 2023 15:14:28 +0200 Subject: [PATCH 51/71] Bump positron-python - Fix test order dependence (https://github.com/posit-dev/positron-python/pull/289) - Format output to match physical console width (#1910) - Enhance error messages with first stack trace and source links (#1175) - Improvements to the fallback statement range detector. Note that these were already fixed in the AST-based detector which applies when the file has no syntax errors: - Multiline statements on the last line of the file (#1271) - Exclude comments from executed code (#1736) --- extensions/positron-python | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-python b/extensions/positron-python index 197c2666e71..52052e39bf2 160000 --- a/extensions/positron-python +++ b/extensions/positron-python @@ -1 +1 @@ -Subproject commit 197c2666e71248a2ff686eed8de431173de075d2 +Subproject commit 52052e39bf2a7ade768ae53d3d8d8dac8b43abd9 From 6df00a6f1fbc4ace7c8eb260c74eeef83ceafb3b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 20 Dec 2023 14:09:46 -0800 Subject: [PATCH 52/71] eng: fix waitServer not working in unit test debug (#201334) Fixes #182341 --- test/unit/electron/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index 81c5752111d..f61514f72f1 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -33,7 +33,7 @@ const minimist = require('minimist'); * dev: boolean; * reporter: string; * 'reporter-options': string; - * 'wait-server': string; + * 'waitServer': string; * timeout: string; * 'crash-reporter-directory': string; * tfs: string; @@ -43,8 +43,8 @@ const minimist = require('minimist'); * }} */ const args = minimist(process.argv.slice(2), { - string: ['grep', 'run', 'runGlob', 'dev', 'reporter', 'reporter-options', 'wait-server', 'timeout', 'crash-reporter-directory', 'tfs'], - boolean: ['build', 'coverage', 'help'], + string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs'], + boolean: ['build', 'coverage', 'help', 'dev'], alias: { 'grep': ['g', 'f'], 'runGlob': ['glob', 'runGrep'], @@ -69,7 +69,7 @@ Options: --dev, --dev-tools, --devTools open dev tools, keep window open, reuse app data --reporter the mocha reporter (default: "spec") --reporter-options the mocha reporter options (default: "") ---wait-server port to connect to and wait before running tests +--waitServer port to connect to and wait before running tests --timeout timeout for tests --crash-reporter-directory crash reporter directory --tfs TFS server URL @@ -232,8 +232,8 @@ app.on('ready', () => { win.webContents.openDevTools(); } - if (args['wait-server']) { - waitForServer(Number(args['wait-server'])).then(sendRun); + if (args.waitServer) { + waitForServer(Number(args.waitServer)).then(sendRun); } else { sendRun(); } From 10fd28fcea9aadc233852572050c6bda61f52285 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Mon, 18 Dec 2023 14:12:31 +0100 Subject: [PATCH 53/71] Switch to frontend message interface --- .../languageRuntime/common/languageRuntime.ts | 39 +++++++++++++++++-- .../common/languageRuntimeEvents.ts | 3 +- .../common/languageRuntimeFrontEndClient.ts | 35 +++++++---------- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts index a49ba3c9d4f..7d6b25bbaf2 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts @@ -20,6 +20,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IModalDialogPromptInstance, IPositronModalDialogsService } from 'vs/workbench/services/positronModalDialogs/common/positronModalDialogs'; import { IOpener, IOpenerService, OpenExternalOptions, OpenInternalOptions } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { LanguageRuntimeEventType } from './languageRuntimeEvents'; /** * LanguageRuntimeInfo class. @@ -713,16 +714,46 @@ export class LanguageRuntimeService extends Disposable implements ILanguageRunti (RuntimeClientType.FrontEnd, {}).then(client => { // Create the frontend client instance wrapping the client instance. const frontendClient = new FrontEndClientInstance(client); + this._register(frontendClient); // When the frontend client instance emits an event, broadcast - // it to Positron. - this._register(frontendClient.onDidEmitEvent(event => { + // it to Positron with the corresponding runtime ID. + this._register(frontendClient.onDidBusy(event => { this._onDidReceiveRuntimeEventEmitter.fire({ runtime_id: runtime.metadata.runtimeId, - event + event: { + name: LanguageRuntimeEventType.Busy, + data: event + } + }); + })); + this._register(frontendClient.onDidShowMessage(event => { + this._onDidReceiveRuntimeEventEmitter.fire({ + runtime_id: runtime.metadata.runtimeId, + event: { + name: LanguageRuntimeEventType.ShowMessage, + data: event + } + }); + })); + this._register(frontendClient.onDidPromptState(event => { + this._onDidReceiveRuntimeEventEmitter.fire({ + runtime_id: runtime.metadata.runtimeId, + event: { + name: LanguageRuntimeEventType.PromptState, + data: event + } + }); + })); + this._register(frontendClient.onDidWorkingDirectory(event => { + this._onDidReceiveRuntimeEventEmitter.fire({ + runtime_id: runtime.metadata.runtimeId, + event: { + name: LanguageRuntimeEventType.WorkingDirectory, + data: event + } }); })); - this._register(frontendClient); }); } diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts index f88935dd1d3..372ff96ae50 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts @@ -7,10 +7,10 @@ export interface LanguageRuntimeEventData { } +// TODO: Generate this in the comm type file export enum LanguageRuntimeEventType { Busy = 'busy', ShowMessage = 'show_message', - ShowHelp = 'show_help', PromptState = 'prompt_state', WorkingDirectory = 'working_directory', } @@ -65,4 +65,3 @@ export interface WorkingDirectoryEvent extends LanguageRuntimeEventData { directory: string; } - diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts index 558ad544426..3bec9b09378 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts @@ -3,9 +3,10 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; import { LanguageRuntimeEventData, LanguageRuntimeEventType } from 'vs/workbench/services/languageRuntime/common/languageRuntimeEvents'; +import { BusyEvent, PositronFrontendComm, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent } from './positronFrontendComm'; /** @@ -58,9 +59,13 @@ export interface IFrontEndClientMessageOutputEvent * the backend know when Positron is connected. */ export class FrontEndClientInstance extends Disposable { + private _comm: PositronFrontendComm; - /** The emitter for runtime client events. */ - private readonly _onDidEmitEvent = this._register(new Emitter()); + /** Emitters for events forwarded from the frontend comm */ + onDidBusy: Event; + onDidShowMessage: Event; + onDidPromptState: Event; + onDidWorkingDirectory: Event; /** * Creates a new frontend client instance. @@ -69,27 +74,15 @@ export class FrontEndClientInstance extends Disposable { * instance and will dispose it when it is disposed. */ constructor( - private readonly _client: - IRuntimeClientInstance, + private readonly _client: IRuntimeClientInstance, ) { super(); this._register(this._client); - this._register(this._client.onDidReceiveData(data => this.handleData(data))); - this.onDidEmitEvent = this._onDidEmitEvent.event; - } - - onDidEmitEvent: Event; - /** - * Handles data received from the backend. - * - * @param data Data received from the backend. - */ - private handleData(data: IFrontEndClientMessageOutput): void { - switch (data.msg_type) { - case FrontEndMessageTypeOutput.Event: - this._onDidEmitEvent.fire(data as IFrontEndClientMessageOutputEvent); - break; - } + this._comm = new PositronFrontendComm(this._client); + this.onDidBusy = this._comm.onDidBusy; + this.onDidShowMessage = this._comm.onDidShowMessage; + this.onDidPromptState = this._comm.onDidPromptState; + this.onDidWorkingDirectory = this._comm.onDidWorkingDirectory; } } From 9d7339a7d1d46b6ae3c8a79e81d3ce21db1b27bb Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Mon, 18 Dec 2023 16:20:28 +0100 Subject: [PATCH 54/71] Remove `ShowHelp` compatibility code --- .../browser/positronHelpService.ts | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/src/vs/workbench/contrib/positronHelp/browser/positronHelpService.ts b/src/vs/workbench/contrib/positronHelp/browser/positronHelpService.ts index 490abed7497..d39fd04b2a7 100644 --- a/src/vs/workbench/contrib/positronHelp/browser/positronHelpService.ts +++ b/src/vs/workbench/contrib/positronHelp/browser/positronHelpService.ts @@ -18,7 +18,6 @@ import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/t import { HelpEntry, IHelpEntry } from 'vs/workbench/contrib/positronHelp/browser/helpEntry'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { LanguageRuntimeEventData, LanguageRuntimeEventType } from 'vs/workbench/services/languageRuntime/common/languageRuntimeEvents'; import { HelpClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeHelpClient'; import { ILanguageRuntime, ILanguageRuntimeService, IRuntimeClientInstance, RuntimeClientType, RuntimeState } from 'vs/workbench/services/languageRuntime/common/languageRuntimeService'; import { ShowHelpEvent } from 'vs/workbench/services/languageRuntime/common/positronHelpComm'; @@ -210,47 +209,6 @@ class PositronHelpService extends Disposable implements IPositronHelpService { } }) ); - - // Register onDidReceiveRuntimeEvent handler. - // - // *** - // TEMPORARY: Currently, some runtimes deliver the ShowHelp event as a - // global event. This code can go away as soon as all runtimes send this - // event over the `positron.help` comm channel instead. - // *** - this._register( - this._languageRuntimeService.onDidReceiveRuntimeEvent(async languageRuntimeGlobalEvent => { - /** - * Custom custom type guard for ShowHelpEvent. - * @param _ The LanguageRuntimeEventData that should be a ShowHelpEvent. - * @returns true if the LanguageRuntimeEventData is a ShowHelpEvent; otherwise, false. - */ - const isShowHelpEvent = (_: LanguageRuntimeEventData): _ is ShowHelpEvent => { - return (_ as ShowHelpEvent).kind !== undefined; - }; - - // Show help event types are supported. - if (languageRuntimeGlobalEvent.event.name !== LanguageRuntimeEventType.ShowHelp) { - return; - } - - // Ensure that the right event data was supplied. - if (!isShowHelpEvent(languageRuntimeGlobalEvent.event.data)) { - this._logService.error(`ShowHelp event supplied unsupported event data.`); - return; - } - - // Get the show help event. - const showHelpEvent = languageRuntimeGlobalEvent.event.data as ShowHelpEvent; - const runtime = this._languageRuntimeService.getRuntime( - languageRuntimeGlobalEvent.runtime_id); - if (runtime) { - this.handleShowHelpEvent(runtime, showHelpEvent); - } else { - this._logService.error(`PositronHelpService could not find runtime ${languageRuntimeGlobalEvent.runtime_id}.`); - } - }) - ); } /** From afb911d46f2b796327f2c8a8d3daba141b1f31d2 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Fri, 22 Dec 2023 09:06:20 +0100 Subject: [PATCH 55/71] Add support for "any" type in the comm generator --- positron/comms/generate-comms.ts | 45 +++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 389d79ec681..d9522edfa40 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -376,9 +376,16 @@ use serde::Serialize; snakeCaseToSentenceCase(context[1])); } const name = o.name ? o.name : context[0] === 'items' ? context[1] : context[0]; + const props = Object.keys(o.properties); + + // Map "any" type to `Value` + if (props.length === 0 && o.additionalProperties === true) { + return yield `pub type ${snakeCaseToSentenceCase(name)} = serde_json::Value;\n\n`; + } + yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; - const props = Object.keys(o.properties); + for (let i = 0; i < props.length; i++) { const key = props[i]; const prop = o.properties[key]; @@ -446,6 +453,9 @@ use serde::Serialize; if (param.schema.enum) { // Use an enum type if the schema has an enum yield `\tpub ${param.name}: ${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)},\n`; + } else if (param.schema.type === 'object' && Object.keys(param.schema.properties).length === 0) { + // Handle the "any" type + yield `\tpub ${param.name}: serde_json::Value,\n`; } else { // Otherwise use the type directly yield `\tpub ${param.name}: ${deriveType(contracts, RustTypeMap, param.name, param.schema)},\n`; @@ -551,6 +561,8 @@ function* createPythonComm(name: string, import enum from dataclasses import dataclass, field +from typing import Dict, List, Union +JsonData = Union[Dict[str, "JsonData"], List["JsonData"], str, int, float, bool, None] `; const contracts = [backend, frontend]; @@ -563,10 +575,18 @@ from dataclasses import dataclass, field context: Array, o: Record) { + let name = o.name ? o.name : context[0] === 'items' ? context[1] : context[0]; + name = snakeCaseToSentenceCase(name); + + // Empty object specs map to `JsonData` + const props = Object.keys(o.properties); + if ((!props || !props.length) && o.additionalProperties === true) { + return yield `${name} = JsonData\n`; + } + // Preamble yield '@dataclass\n'; - const name = o.name ? o.name : context[0] === 'items' ? context[1] : context[0]; - yield `class ${snakeCaseToSentenceCase(name)}:\n`; + yield `class ${name}:\n`; // Docstring if (o.description) { @@ -754,15 +774,20 @@ from dataclasses import dataclass, field * @param description The description of the schema * @param properties The properties of the schema * @param required An array of required properties + * @param additionalProperties Whether additional properties are allowed. + * Currently only used for "any" objects. * * @returns A generator that yields the Typescript code for an interface * representing the schema */ -function* createTypescriptInterface(contracts: Array, +function* createTypescriptInterface( + contracts: Array, name: string, description: string, properties: Record, - required: Array): Generator { + required: Array, + additionalProperties?: boolean, +): Generator { if (!description) { throw new Error(`No description for '${name}'; please add a description to the schema`); @@ -772,7 +797,12 @@ function* createTypescriptInterface(contracts: Array, yield ' */\n'; yield `export interface ${snakeCaseToSentenceCase(name)} {\n`; if (!properties || Object.keys(properties).length === 0) { - throw new Error(`No properties for '${name}'; please add properties to the schema`); + if (!additionalProperties) { + throw new Error(`No properties for '${name}'; please add properties to the schema`); + } + + // If `additionalProperties` is true, treat empty object specs as an "any" object + yield '\t[k: string]: unknown;\n'; } for (const prop of Object.keys(properties)) { const schema = properties[prop]; @@ -838,8 +868,9 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co const description = o.description ? o.description : snakeCaseToSentenceCase(context[0]) + ' in ' + snakeCaseToSentenceCase(context[1]); + const additionalProperties = o.additionalProperties ? o.additionalProperties : false; yield* createTypescriptInterface(contracts, name, description, o.properties, - o.required ? o.required : []); + o.required ? o.required : [], additionalProperties); }); // Create enums for all enum types From dcfe77ca6ea2b0e858310d713432a8fa03ba111d Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Fri, 22 Dec 2023 09:06:41 +0100 Subject: [PATCH 56/71] Add `call_method` method in the frontend comm --- .../src/LanguageRuntimeAdapter.ts | 10 ++++- positron/comms/frontend-backend-openrpc.json | 38 ++++++++++++++++++- .../common/positronFrontendComm.ts | 30 +++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts b/extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts index efa5563c486..1302ebdedee 100644 --- a/extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts +++ b/extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts @@ -117,11 +117,17 @@ export class LanguageRuntimeAdapter // Create the request. This uses a JSON-RPC 2.0 format, with an // additional `msg_type` field to indicate that this is a request type // for the frontend comm. + // + // NOTE: Currently using nested RPC messages for convenience but + // we'd like to do better const request = { msg_type: 'rpc_request', jsonrpc: '2.0', - method: method, - params: args, + method: 'call_method', + params: { + method, + params: args + }, }; // Return a promise that resolves when the server side of the frontend diff --git a/positron/comms/frontend-backend-openrpc.json b/positron/comms/frontend-backend-openrpc.json index 136d4feb1e2..1368b6ae4da 100644 --- a/positron/comms/frontend-backend-openrpc.json +++ b/positron/comms/frontend-backend-openrpc.json @@ -4,5 +4,41 @@ "title": "Frontend Backend", "version": "1.0.0" }, - "methods": [] + "methods": [ + { + "name": "call_method", + "summary": "Run a method in the interpreter and return the result to the frontend", + "description": "Unlike other RPC methods, `call_method` calls into methods implemented in the interpreter and returns the result back to the frontend using an implementation-defined serialization scheme.", + "params": [ + { + "name": "method", + "description": "The method to call inside the interpreter", + "schema": { + "type": "string" + } + }, + { + "name": "params", + "description": "The parameters for `method`", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {}, + "additionalProperties": true + } + } + } + ], + "result": { + "schema": { + "name": "call_method_result", + "description": "The method result", + "type": "object", + "properties": {}, + "additionalProperties": true + } + } + } + ] } diff --git a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts index 8df37a9c857..ae532ec35dc 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts @@ -10,6 +10,20 @@ import { Event } from 'vs/base/common/event'; import { PositronBaseComm } from 'vs/workbench/services/languageRuntime/common/positronBaseComm'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; +/** + * Items in Params + */ +export interface Params { + [k: string]: unknown; +} + +/** + * The method result + */ +export interface CallMethodResult { + [k: string]: unknown; +} + /** * Event: Change in backend's busy/idle status */ @@ -68,6 +82,22 @@ export class PositronFrontendComm extends PositronBaseComm { this.onDidWorkingDirectory = super.createEventEmitter('working_directory', ['directory']); } + /** + * Run a method in the interpreter and return the result to the frontend + * + * Unlike other RPC methods, `call_method` calls into methods implemented + * in the interpreter and returns the result back to the frontend using + * an implementation-defined serialization scheme. + * + * @param method The method to call inside the interpreter + * @param params The parameters for `method` + * + * @returns The method result + */ + callMethod(method: string, params: Array): Promise { + return super.performRpc('call_method', ['method', 'params'], [method, params]); + } + /** * Change in backend's busy/idle status From 80d94bddb8e2317ba1b5cde0afc44e522cbd7762 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Fri, 22 Dec 2023 11:40:07 +0100 Subject: [PATCH 57/71] Rename Rust struct fields to camelCase --- positron/comms/generate-comms.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index d9522edfa40..8ecd886b4b3 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -384,6 +384,7 @@ use serde::Serialize; } yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; + yield '#[serde(rename_all = "camelCase")]\n'; yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; for (let i = 0; i < props.length; i++) { @@ -444,6 +445,7 @@ use serde::Serialize; snakeCaseToSentenceCase(method.name) + ` ` + `method.`); yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; + yield '#[serde(rename_all = "camelCase")]\n'; yield `pub struct ${snakeCaseToSentenceCase(method.name)}Params {\n`; for (let i = 0; i < method.params.length; i++) { const param = method.params[i]; From 939a1893c3dda68fd8192cf586793016ef18ae12 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Fri, 22 Dec 2023 16:02:59 +0100 Subject: [PATCH 58/71] Remove old event generation infrastructure --- positron/events.yaml | 54 ----- positron/scripts/generate-events.ts | 228 ------------------ positron/scripts/positron-events.ts | 22 -- .../positron/mainThreadLanguageRuntime.ts | 3 +- .../common/languageRuntimeEvents.ts | 56 ----- 5 files changed, 2 insertions(+), 361 deletions(-) delete mode 100644 positron/events.yaml delete mode 100644 positron/scripts/generate-events.ts delete mode 100644 positron/scripts/positron-events.ts diff --git a/positron/events.yaml b/positron/events.yaml deleted file mode 100644 index 431d00e93ab..00000000000 --- a/positron/events.yaml +++ /dev/null @@ -1,54 +0,0 @@ -- name: Busy - comment: > - Represents a change in the runtime's busy state. - - Note that this represents the busy state of the underlying computation engine, - not the busy state of the kernel. - - The kernel is busy when it is processing a request, - but the runtime is busy only when a computation is running. - params: - - name: busy - type: boolean - comment: Whether the runtime is busy. - -- name: ShowMessage - comment: > - Use this event to show a message to the user. - params: - - name: message - type: string - comment: The message to show to the user. - -- name: ShowHelp - comment: > - Show help content in the Help pane. - params: - - name: content - type: string - comment: The help content to be shown. - - name: kind - type: string - comment: The content help type. Must be one of 'html', 'markdown', or 'url'. - - name: focus - type: boolean - comment: Focus the Help pane after the Help content has been rendered? - -- name: PromptState - comment: > - Update strings of future input and continuation prompts. - params: - - name: inputPrompt - type: string - comment: String for future input prompts. - - name: continuationPrompt - type: string - comment: String for future continuation prompts. - -- name: WorkingDirectory - comment: > - Change the displayed working directory for the interpreter. - params: - - name: directory - type: string - comment: The new working directory. diff --git a/positron/scripts/generate-events.ts b/positron/scripts/generate-events.ts deleted file mode 100644 index 21ff94830cb..00000000000 --- a/positron/scripts/generate-events.ts +++ /dev/null @@ -1,228 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -import { existsSync, readFileSync, writeFileSync } from 'fs'; -import { events, PositronEventDefinition } from './positron-events'; - -type TypeMap = { - [key: string]: string; -}; - -const rustTypesMap = { - boolean: 'bool', - string: 'String', - integer: 'i32', -}; - -const tsTypesMap = { - boolean: 'boolean', - string: 'string', - integer: 'integer' -}; - -function camel(value: string): string { - - let snakeCased = value.replace(/[A-Z]/g, (letter) => { - return `_${letter.toLowerCase()}`; - }); - - if (snakeCased.startsWith('_')) { - snakeCased = snakeCased.substring(1); - } - - return snakeCased.replace(/_event$/, ''); - -} - -function snake(str: string) { - return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); -} - -function indent(value: string, indent: string): string { - return value.replace(/(^|\n)/g, `$1${indent}`); -} - -function generateRustEvent(event: PositronEventDefinition) { - - const lines: string[] = []; - - const comment = event.comment.trimEnd().replace(/(^|\n)/g, '$1/// '); - lines.push(comment); - lines.push(`#[positron::event("${camel(event.name)}")]`); - lines.push(`pub struct ${event.name}Event {`); - lines.push(''); - - for (const param of event.params) { - lines.push(` /// ${param.comment}`); - lines.push(` pub ${snake(param.name)}: ${rustTypesMap[param.type]},`); - lines.push(''); - } - - lines.push('}'); - lines.push(''); - - return lines.join('\n'); - -} - -function generateRustPositronEventEnum() { - - const lines: string[] = []; - - lines.push('#[derive(Debug, Clone)]'); - lines.push('pub enum PositronEvent {'); - for (const event of events) { - lines.push(` ${event.name}(${event.name}Event),`); - } - lines.push('}'); - - return lines.join('\n'); - -} - -function generateRustClientEventHelper() { - - const dispatchLines = events.map((event) => { - return `PositronEvent::${event.name}(data) => Self::as_evt(data),`; - }).join('\n'); - - return `impl From for ClientEvent { - fn from(event: PositronEvent) -> Self { - match event { -${indent(dispatchLines, ' ')} - } - } -}`; - -} - -function updateRustEventsFile(rustEventsFile: string) { - - // Generate event definitions for Ark - const rustEvents = events.map(generateRustEvent); - - const currentYear = (new Date()).getFullYear(); - const rustHeader = `// -// mod.rs -// -// Copyright (C) ${currentYear} Posit Software, PBC. All rights reserved. -// -// -// Auto-generated by 'positron/scripts/generate-events.ts'. -// Please do not modify this file directly. -// - -use crate::positron; - -pub trait PositronEventType { - fn event_type(&self) -> String; -} -`; - - rustEvents.unshift(rustHeader); - - const eventsEnum = generateRustPositronEventEnum(); - rustEvents.push(eventsEnum); - rustEvents.push(''); - - writeFileSync(rustEventsFile, rustEvents.join('\n')); - -} - -function updateRustClientEventsFile(path: string) { - - const contents = readFileSync(path, { encoding: 'utf-8' }); - const lines = contents.split(/\r?\n/); - - const startIndex = lines.findIndex((line) => { - return line.endsWith('/** begin rust-client-event */'); - }); - - const endIndex = lines.findIndex((line) => { - return line.endsWith('/** end rust-client-event */'); - }); - - const replacement = generateRustClientEventHelper(); - lines.splice(startIndex + 1, endIndex - startIndex - 1, replacement); - - const replacedContents = lines.join('\n'); - writeFileSync(path, replacedContents); - - -} - -function generateLanguageRuntimeEventTypeEnum() { - - const lines: string[] = []; - lines.push('export enum LanguageRuntimeEventType {'); - for (const event of events) { - const lhs = event.name; - const rhs = camel(lhs); - lines.push(`\t${lhs} = '${rhs}',`); - } - lines.push('}'); - - return lines.join('\n'); - -} - -function generateLanguageRuntimeEventDefinitions() { - - const lines: string[] = []; - - for (const event of events) { - - const comment = event.comment.trimEnd().replace(/(^|\n)/g, '$1// '); - lines.push(comment); - lines.push(`export interface ${event.name}Event extends LanguageRuntimeEventData {`); - lines.push(''); - for (const param of event.params) { - lines.push(`\t/** ${param.comment} */`); - lines.push(`\t${param.name}: ${tsTypesMap[param.type]};`); - lines.push(''); - } - lines.push('}'); - lines.push(''); - } - - return lines.join('\n'); - -} - -function generateLanguageRuntimeEventsFile(languageRuntimeEventsFile: string) { - - const languageRuntimeEventEnum = generateLanguageRuntimeEventTypeEnum(); - const languageRuntimeEventDefinitions = generateLanguageRuntimeEventDefinitions(); - const currentYear = (new Date()).getFullYear(); - const generatedContents = `/*--------------------------------------------------------------------------------------------- - * Copyright (C) ${currentYear} Posit Software, PBC. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -// This file was automatically generated by 'positron/scripts/generate-events.ts'. -// Please do not modify this file directly. - -export interface LanguageRuntimeEventData { } - -${languageRuntimeEventEnum} - -${languageRuntimeEventDefinitions} -`; - - writeFileSync(languageRuntimeEventsFile, generatedContents); - -} - -// Move to project root directory. -while (!existsSync(`${process.cwd()}/.git`)) { - process.chdir('..'); -} - -const rustEventsFile = '../amalthea/crates/amalthea/src/events/mod.rs'; -updateRustEventsFile(rustEventsFile); - -const rustClientEventsFile = '../amalthea/crates/amalthea/src/wire/client_event.rs'; -updateRustClientEventsFile(rustClientEventsFile); - -const languageRuntimeEventsFile = 'src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts'; -generateLanguageRuntimeEventsFile(languageRuntimeEventsFile); diff --git a/positron/scripts/positron-events.ts b/positron/scripts/positron-events.ts deleted file mode 100644 index 32614c2ccae..00000000000 --- a/positron/scripts/positron-events.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2022 Posit Software, PBC. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -import { readFileSync } from 'fs'; -import yaml from 'js-yaml'; - -export interface PositronEventDefinitionParam { - name: string; - type: string; - comment: string; -} - -export interface PositronEventDefinition { - name: string; - comment: string; - params: PositronEventDefinitionParam[]; -} - -const eventsYamlFile = `${__dirname}/../events.yaml`; -const eventsYamlContents = readFileSync(eventsYamlFile, { encoding: 'utf-8' }); -export const events = yaml.load(eventsYamlContents) as PositronEventDefinition[]; diff --git a/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts b/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts index 70f09760253..0079706417d 100644 --- a/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts +++ b/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts @@ -20,11 +20,12 @@ import { DeferredPromise } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; import { IPositronPlotsService } from 'vs/workbench/services/positronPlots/common/positronPlots'; import { IPositronIPyWidgetsService, MIME_TYPE_WIDGET_STATE, MIME_TYPE_WIDGET_VIEW } from 'vs/workbench/services/positronIPyWidgets/common/positronIPyWidgetsService'; -import { BusyEvent, LanguageRuntimeEventType, PromptStateEvent, WorkingDirectoryEvent } from 'vs/workbench/services/languageRuntime/common/languageRuntimeEvents'; +import { LanguageRuntimeEventType } from 'vs/workbench/services/languageRuntime/common/languageRuntimeEvents'; import { IPositronHelpService } from 'vs/workbench/contrib/positronHelp/browser/positronHelpService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IRuntimeClientEvent } from 'vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient'; import { URI } from 'vs/base/common/uri'; +import { BusyEvent, PromptStateEvent, WorkingDirectoryEvent } from 'vs/workbench/services/languageRuntime/common/positronFrontendComm'; /** * Represents a language runtime event (for example a message or state change) diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts index 372ff96ae50..6bde9bc2b25 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts @@ -2,11 +2,6 @@ * Copyright (C) 2023 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -// This file was automatically generated by 'positron/scripts/generate-events.ts'. -// Please do not modify this file directly. - -export interface LanguageRuntimeEventData { } - // TODO: Generate this in the comm type file export enum LanguageRuntimeEventType { Busy = 'busy', @@ -14,54 +9,3 @@ export enum LanguageRuntimeEventType { PromptState = 'prompt_state', WorkingDirectory = 'working_directory', } - -// Represents a change in the runtime's busy state. -// Note that this represents the busy state of the underlying computation engine, not the busy state of the kernel. -// The kernel is busy when it is processing a request, but the runtime is busy only when a computation is running. -export interface BusyEvent extends LanguageRuntimeEventData { - - /** Whether the runtime is busy. */ - busy: boolean; - -} - -// Use this event to show a message to the user. -export interface ShowMessageEvent extends LanguageRuntimeEventData { - - /** The message to show to the user. */ - message: string; - -} - -// Show help content in the Help pane. -export interface ShowHelpEvent extends LanguageRuntimeEventData { - - /** The help content to be shown. */ - content: string; - - /** The content help type. Must be one of 'html', 'markdown', or 'url'. */ - kind: string; - - /** Focus the Help pane after the Help content has been rendered? */ - focus: boolean; - -} - -// Update strings of future input and continuation prompts. -export interface PromptStateEvent extends LanguageRuntimeEventData { - - /** String for future input prompts. */ - inputPrompt: string; - - /** String for future continuation prompts. */ - continuationPrompt: string; - -} - -// Change the displayed working directory for the interpreter. -export interface WorkingDirectoryEvent extends LanguageRuntimeEventData { - - /** The new working directory. */ - directory: string; - -} From 79600effd530eead37770daf513cd488899185ef Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Fri, 22 Dec 2023 16:22:08 +0100 Subject: [PATCH 59/71] Move generation of event enum type to new infra --- positron/comms/generate-comms.ts | 13 ++++++++++++- src/positron-dts/positron.d.ts | 2 -- .../browser/positron/mainThreadLanguageRuntime.ts | 9 ++++----- .../browser/components/actionBar.tsx | 4 ++-- .../languageRuntime/common/languageRuntime.ts | 10 +++++----- .../languageRuntime/common/languageRuntimeEvents.ts | 11 ----------- .../common/languageRuntimeFrontEndClient.ts | 7 +++---- .../languageRuntime/common/positronFrontendComm.ts | 7 +++++++ .../languageRuntime/common/positronHelpComm.ts | 4 ++++ .../languageRuntime/common/positronPlotComm.ts | 4 ++++ .../languageRuntime/common/positronVariablesComm.ts | 4 ++++ 11 files changed, 45 insertions(+), 30 deletions(-) delete mode 100644 src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 8ecd886b4b3..6be015048b2 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -917,15 +917,22 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co } if (frontend) { + let events: string[] = []; + for (const method of frontend.methods) { // Ignore methods that have a result; we're generating event types here if (method.result) { continue; } + + // Collect enum fields + const sentenceName = snakeCaseToSentenceCase(method.name); + events.push(`\t${sentenceName} = '${method.name}'`) + yield '/**\n'; yield formatComment(' * ', `Event: ${method.summary}`); yield ' */\n'; - yield `export interface ${snakeCaseToSentenceCase(method.name)}Event {\n`; + yield `export interface ${sentenceName}Event {\n`; for (const param of method.params) { yield '\t/**\n'; yield formatComment('\t * ', `${param.description}`); @@ -940,6 +947,10 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co } yield '}\n\n'; } + + yield `export enum ${snakeCaseToSentenceCase(name)}Event {\n`; + yield events.join(',\n'); + yield '\n}\n\n'; } yield `export class Positron${snakeCaseToSentenceCase(name)}Comm extends PositronBaseComm {\n`; diff --git a/src/positron-dts/positron.d.ts b/src/positron-dts/positron.d.ts index 77ed3cf0c49..3ed3c8bfa91 100644 --- a/src/positron-dts/positron.d.ts +++ b/src/positron-dts/positron.d.ts @@ -197,8 +197,6 @@ declare module 'positron' { type: LanguageRuntimeMessageType; } - export interface LanguageRuntimeEventData { } - /** LanguageRuntimeOutput is a LanguageRuntimeMessage representing output (text, plots, etc.) */ export interface LanguageRuntimeOutput extends LanguageRuntimeMessage { /** A record of data MIME types to the associated data, e.g. `text/plain` => `'hello world'` */ diff --git a/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts b/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts index 0079706417d..8edaec8df7b 100644 --- a/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts +++ b/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts @@ -20,12 +20,11 @@ import { DeferredPromise } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; import { IPositronPlotsService } from 'vs/workbench/services/positronPlots/common/positronPlots'; import { IPositronIPyWidgetsService, MIME_TYPE_WIDGET_STATE, MIME_TYPE_WIDGET_VIEW } from 'vs/workbench/services/positronIPyWidgets/common/positronIPyWidgetsService'; -import { LanguageRuntimeEventType } from 'vs/workbench/services/languageRuntime/common/languageRuntimeEvents'; import { IPositronHelpService } from 'vs/workbench/contrib/positronHelp/browser/positronHelpService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IRuntimeClientEvent } from 'vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient'; import { URI } from 'vs/base/common/uri'; -import { BusyEvent, PromptStateEvent, WorkingDirectoryEvent } from 'vs/workbench/services/languageRuntime/common/positronFrontendComm'; +import { BusyEvent, FrontendEvent, PromptStateEvent, WorkingDirectoryEvent } from 'vs/workbench/services/languageRuntime/common/positronFrontendComm'; /** * Represents a language runtime event (for example a message or state change) @@ -142,7 +141,7 @@ class ExtHostLanguageRuntimeAdapter implements ILanguageRuntime { } const ev = globalEvent.event; - if (ev.name === LanguageRuntimeEventType.PromptState) { + if (ev.name === FrontendEvent.PromptState) { // Update config before propagating event const state = ev.data as PromptStateEvent; @@ -161,11 +160,11 @@ class ExtHostLanguageRuntimeAdapter implements ILanguageRuntime { // Don't include new state in event, clients should // inspect the runtime's dyn state instead this.emitDidReceiveRuntimeMessagePromptConfig(); - } else if (ev.name === LanguageRuntimeEventType.Busy) { + } else if (ev.name === FrontendEvent.Busy) { // Update busy state const busy = ev.data as BusyEvent; this.dynState.busy = busy.busy; - } else if (ev.name === LanguageRuntimeEventType.WorkingDirectory) { + } else if (ev.name === FrontendEvent.WorkingDirectory) { // Update current working directory const dir = ev.data as WorkingDirectoryEvent; this.dynState.currentWorkingDirectory = dir.directory; diff --git a/src/vs/workbench/contrib/positronConsole/browser/components/actionBar.tsx b/src/vs/workbench/contrib/positronConsole/browser/components/actionBar.tsx index cbc5e540969..0d2f57eedca 100644 --- a/src/vs/workbench/contrib/positronConsole/browser/components/actionBar.tsx +++ b/src/vs/workbench/contrib/positronConsole/browser/components/actionBar.tsx @@ -13,12 +13,12 @@ import { PositronActionBar } from 'vs/platform/positronActionBar/browser/positro import { ActionBarRegion } from 'vs/platform/positronActionBar/browser/components/actionBarRegion'; import { ActionBarButton } from 'vs/platform/positronActionBar/browser/components/actionBarButton'; import { ActionBarSeparator } from 'vs/platform/positronActionBar/browser/components/actionBarSeparator'; -import { LanguageRuntimeEventType } from 'vs/workbench/services/languageRuntime/common/languageRuntimeEvents'; import { usePositronConsoleContext } from 'vs/workbench/contrib/positronConsole/browser/positronConsoleContext'; import { PositronActionBarContextProvider } from 'vs/platform/positronActionBar/browser/positronActionBarContext'; import { ILanguageRuntime, RuntimeState } from 'vs/workbench/services/languageRuntime/common/languageRuntimeService'; import { PositronConsoleState } from 'vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService'; import { ConsoleInstanceMenuButton } from 'vs/workbench/contrib/positronConsole/browser/components/consoleInstanceMenuButton'; +import { FrontendEvent } from 'vs/workbench/services/languageRuntime/common/positronFrontendComm'; /** * Constants. @@ -188,7 +188,7 @@ export const ActionBar = (props: ActionBarProps) => { // Listen for changes to the working directory. disposableRuntimeStore.add(runtime.onDidReceiveRuntimeClientEvent((event) => { - if (event.name === LanguageRuntimeEventType.WorkingDirectory) { + if (event.name === FrontendEvent.WorkingDirectory) { setDirectoryLabel(runtime.dynState.currentWorkingDirectory); } })); diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts index 7d6b25bbaf2..ea8027d13b9 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts @@ -20,7 +20,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IModalDialogPromptInstance, IPositronModalDialogsService } from 'vs/workbench/services/positronModalDialogs/common/positronModalDialogs'; import { IOpener, IOpenerService, OpenExternalOptions, OpenInternalOptions } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; -import { LanguageRuntimeEventType } from './languageRuntimeEvents'; +import { FrontendEvent } from './positronFrontendComm'; /** * LanguageRuntimeInfo class. @@ -722,7 +722,7 @@ export class LanguageRuntimeService extends Disposable implements ILanguageRunti this._onDidReceiveRuntimeEventEmitter.fire({ runtime_id: runtime.metadata.runtimeId, event: { - name: LanguageRuntimeEventType.Busy, + name: FrontendEvent.Busy, data: event } }); @@ -731,7 +731,7 @@ export class LanguageRuntimeService extends Disposable implements ILanguageRunti this._onDidReceiveRuntimeEventEmitter.fire({ runtime_id: runtime.metadata.runtimeId, event: { - name: LanguageRuntimeEventType.ShowMessage, + name: FrontendEvent.ShowMessage, data: event } }); @@ -740,7 +740,7 @@ export class LanguageRuntimeService extends Disposable implements ILanguageRunti this._onDidReceiveRuntimeEventEmitter.fire({ runtime_id: runtime.metadata.runtimeId, event: { - name: LanguageRuntimeEventType.PromptState, + name: FrontendEvent.PromptState, data: event } }); @@ -749,7 +749,7 @@ export class LanguageRuntimeService extends Disposable implements ILanguageRunti this._onDidReceiveRuntimeEventEmitter.fire({ runtime_id: runtime.metadata.runtimeId, event: { - name: LanguageRuntimeEventType.WorkingDirectory, + name: FrontendEvent.WorkingDirectory, data: event } }); diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts deleted file mode 100644 index 6bde9bc2b25..00000000000 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntimeEvents.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -// TODO: Generate this in the comm type file -export enum LanguageRuntimeEventType { - Busy = 'busy', - ShowMessage = 'show_message', - PromptState = 'prompt_state', - WorkingDirectory = 'working_directory', -} diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts index 3bec9b09378..85afa9bb9dc 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts @@ -5,8 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; -import { LanguageRuntimeEventData, LanguageRuntimeEventType } from 'vs/workbench/services/languageRuntime/common/languageRuntimeEvents'; -import { BusyEvent, PositronFrontendComm, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent } from './positronFrontendComm'; +import { BusyEvent, FrontendEvent, PositronFrontendComm, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent } from './positronFrontendComm'; /** @@ -40,8 +39,8 @@ export interface IFrontEndClientMessageOutput { * An event from the backend. */ export interface IRuntimeClientEvent { - name: LanguageRuntimeEventType; - data: LanguageRuntimeEventData; + name: FrontendEvent; + data: any; } /** diff --git a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts index ae532ec35dc..ac8e6abd57f 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts @@ -73,6 +73,13 @@ export interface WorkingDirectoryEvent { } +export enum FrontendEvent { + Busy = 'busy', + ShowMessage = 'show_message', + PromptState = 'prompt_state', + WorkingDirectory = 'working_directory' +} + export class PositronFrontendComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); diff --git a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts index acc573a10e5..d1506decc35 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts @@ -40,6 +40,10 @@ export interface ShowHelpEvent { } +export enum HelpEvent { + ShowHelp = 'show_help' +} + export class PositronHelpComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); diff --git a/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts b/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts index 339102be28b..5634f07ecc0 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts @@ -32,6 +32,10 @@ export interface PlotResult { export interface UpdateEvent { } +export enum PlotEvent { + Update = 'update' +} + export class PositronPlotComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index 2a4632ac970..a4a2e7251d6 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -137,6 +137,10 @@ export interface UpdateEvent { } +export enum VariablesEvent { + Update = 'update' +} + export class PositronVariablesComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); From 3452db6341d31d94c32cd72c2d9621ce7241cd89 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Fri, 22 Dec 2023 20:40:24 +0100 Subject: [PATCH 60/71] Use snake_case in TS message fields --- positron/comms/generate-comms.ts | 4 +--- .../api/browser/positron/mainThreadLanguageRuntime.ts | 4 ++-- .../services/languageRuntime/common/positronFrontendComm.ts | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 6be015048b2..fd21bf2b8a5 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -384,7 +384,6 @@ use serde::Serialize; } yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; - yield '#[serde(rename_all = "camelCase")]\n'; yield `pub struct ${snakeCaseToSentenceCase(name)} {\n`; for (let i = 0; i < props.length; i++) { @@ -445,7 +444,6 @@ use serde::Serialize; snakeCaseToSentenceCase(method.name) + ` ` + `method.`); yield '#[derive(Debug, Serialize, Deserialize, PartialEq)]\n'; - yield '#[serde(rename_all = "camelCase")]\n'; yield `pub struct ${snakeCaseToSentenceCase(method.name)}Params {\n`; for (let i = 0; i < method.params.length; i++) { const param = method.params[i]; @@ -937,7 +935,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co yield '\t/**\n'; yield formatComment('\t * ', `${param.description}`); yield '\t */\n'; - yield `\t${snakeCaseToCamelCase(param.name)}: `; + yield `\t${param.name}: `; if (param.schema.type === 'string' && param.schema.enum) { yield `${snakeCaseToSentenceCase(method.name)}${snakeCaseToSentenceCase(param.name)}`; } else { diff --git a/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts b/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts index 8edaec8df7b..8e17c209e55 100644 --- a/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts +++ b/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts @@ -147,8 +147,8 @@ class ExtHostLanguageRuntimeAdapter implements ILanguageRuntime { // Runtimes might supply prompts with trailing whitespace (e.g. R, // Python) that we trim here because we add our own whitespace later on - const inputPrompt = state.inputPrompt?.trimEnd(); - const continuationPrompt = state.continuationPrompt?.trimEnd(); + const inputPrompt = state.input_prompt?.trimEnd(); + const continuationPrompt = state.continuation_prompt?.trimEnd(); if (inputPrompt) { this.dynState.inputPrompt = inputPrompt; diff --git a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts index ac8e6abd57f..53178e69b16 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts @@ -53,12 +53,12 @@ export interface PromptStateEvent { /** * Prompt for primary input. */ - inputPrompt: string; + input_prompt: string; /** * Prompt for incomplete input. */ - continuationPrompt: string; + continuation_prompt: string; } From f00b4704640279a4a5266e6e0b5ca1aa69b5cec7 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Sat, 23 Dec 2023 10:55:42 +0100 Subject: [PATCH 61/71] Fix extra generated space in Python comms --- positron/comms/generate-comms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index fd21bf2b8a5..747965dca7a 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -552,7 +552,7 @@ function* createPythonComm(name: string, frontend: any, backend: any): Generator { yield `# -# Copyright (C) ${year} Posit Software, PBC. All rights reserved. +# Copyright (C) ${year} Posit Software, PBC. All rights reserved. # # From b0016d9e0647e609a4fe0b802c8d803a77d8de58 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Sat, 23 Dec 2023 11:13:56 +0100 Subject: [PATCH 62/71] Bump positron-python --- extensions/positron-python | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-python b/extensions/positron-python index 52052e39bf2..ae5c444adf4 160000 --- a/extensions/positron-python +++ b/extensions/positron-python @@ -1 +1 @@ -Subproject commit 52052e39bf2a7ade768ae53d3d8d8dac8b43abd9 +Subproject commit ae5c444adf466d50cdef610824094b1cfb1db666 From 600e2223bcb435a4d2e0c96b83ed44c933d4be8d Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Sat, 23 Dec 2023 11:15:29 +0100 Subject: [PATCH 63/71] Bump ark to 0.1.40 --- extensions/positron-r/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-r/package.json b/extensions/positron-r/package.json index 499c399859f..483b8219c65 100644 --- a/extensions/positron-r/package.json +++ b/extensions/positron-r/package.json @@ -503,7 +503,7 @@ }, "positron": { "binaryDependencies": { - "ark": "0.1.39" + "ark": "0.1.40" } } } From 734af2db2a730af0c2d50ee2339c73cfc7ac6f82 Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 26 Dec 2023 17:13:12 +0200 Subject: [PATCH 64/71] Bump positron-python - Merge vscode-python 2023.22.1 from upstream (https://github.com/posit-dev/positron-python/pull/297) - Update the active environment when starting a runtime; opt into the `pythonTerminalEnvVarActivation` experiment for better terminal integration (#1491) --- extensions/positron-python | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-python b/extensions/positron-python index ae5c444adf4..3ad4a90b9d0 160000 --- a/extensions/positron-python +++ b/extensions/positron-python @@ -1 +1 @@ -Subproject commit ae5c444adf466d50cdef610824094b1cfb1db666 +Subproject commit 3ad4a90b9d0fa6c2916ac01729570a28c421b76e From 66fb8940ae820c182e940a1c1f7d539a813c88cd Mon Sep 17 00:00:00 2001 From: seem Date: Wed, 27 Dec 2023 19:17:28 +0200 Subject: [PATCH 65/71] Bump positron-python Another attempt to fix hanging Jedi completions (https://github.com/posit-dev/positron-python/pull/300). --- extensions/positron-python | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-python b/extensions/positron-python index 3ad4a90b9d0..24e8f52bb0c 160000 --- a/extensions/positron-python +++ b/extensions/positron-python @@ -1 +1 @@ -Subproject commit 3ad4a90b9d0fa6c2916ac01729570a28c421b76e +Subproject commit 24e8f52bb0c663a23ab2ecc8e366d7298d30c0c1 From 775b67018a47b1b1ae72e4d50aa7e394fc3150e9 Mon Sep 17 00:00:00 2001 From: seem Date: Wed, 27 Dec 2023 14:45:28 +0200 Subject: [PATCH 66/71] Add the `open_editor` frontend event The `open_editor` event lets language runtimes instruct Positron to open an editor with a given file and selection (line and column numbers). This is used in the Python runtime (PR: https://github.com/posit-dev/positron-python/pull/299) to redirect `obj??` (which in IPython shows the source code of `obj`) to open the file defining `obj` at the defining line. Addresses #762. --- positron/comms/frontend-frontend-openrpc.json | 28 ++++++++++++++++++ .../positron/mainThreadLanguageRuntime.ts | 17 +++++++++-- .../languageRuntime/common/languageRuntime.ts | 9 ++++++ .../common/languageRuntimeFrontEndClient.ts | 4 ++- .../common/positronFrontendComm.ts | 29 +++++++++++++++++++ 5 files changed, 84 insertions(+), 3 deletions(-) diff --git a/positron/comms/frontend-frontend-openrpc.json b/positron/comms/frontend-frontend-openrpc.json index 2afa6442e2b..fe06b2b81ed 100644 --- a/positron/comms/frontend-frontend-openrpc.json +++ b/positron/comms/frontend-frontend-openrpc.json @@ -19,6 +19,34 @@ } ] }, + { + "name": "open_editor", + "summary": "Open an editor", + "description": "This event is used to open an editor with a given file and selection.", + "params": [ + { + "name": "file", + "description": "The path of the file to open", + "schema": { + "type": "string" + } + }, + { + "name": "line", + "description": "The line number to jump to", + "schema": { + "type": "integer" + } + }, + { + "name": "column", + "description": "The column number to jump to", + "schema": { + "type": "integer" + } + } + ] + }, { "name": "show_message", "summary": "Show a message", diff --git a/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts b/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts index 8e17c209e55..4779b9cab71 100644 --- a/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts +++ b/src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts @@ -24,7 +24,9 @@ import { IPositronHelpService } from 'vs/workbench/contrib/positronHelp/browser/ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IRuntimeClientEvent } from 'vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient'; import { URI } from 'vs/base/common/uri'; -import { BusyEvent, FrontendEvent, PromptStateEvent, WorkingDirectoryEvent } from 'vs/workbench/services/languageRuntime/common/positronFrontendComm'; +import { BusyEvent, FrontendEvent, OpenEditorEvent, PromptStateEvent, WorkingDirectoryEvent } from 'vs/workbench/services/languageRuntime/common/positronFrontendComm'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; /** * Represents a language runtime event (for example a message or state change) @@ -104,6 +106,7 @@ class ExtHostLanguageRuntimeAdapter implements ILanguageRuntime { private readonly _languageRuntimeService: ILanguageRuntimeService, private readonly _logService: ILogService, private readonly _notebookService: INotebookService, + private readonly _editorService: IEditorService, private readonly _proxy: ExtHostLanguageRuntimeShape) { // Bind events to emitters @@ -164,6 +167,14 @@ class ExtHostLanguageRuntimeAdapter implements ILanguageRuntime { // Update busy state const busy = ev.data as BusyEvent; this.dynState.busy = busy.busy; + } else if (ev.name === FrontendEvent.OpenEditor) { + // Open an editor + const ed = ev.data as OpenEditorEvent; + const editor: ITextResourceEditorInput = { + resource: URI.file(ed.file), + options: { selection: { startLineNumber: ed.line, startColumn: ed.column } } + }; + this._editorService.openEditor(editor); } else if (ev.name === FrontendEvent.WorkingDirectory) { // Update current working directory const dir = ev.data as WorkingDirectoryEvent; @@ -938,7 +949,8 @@ export class MainThreadLanguageRuntime implements MainThreadLanguageRuntimeShape @IPositronPlotsService private readonly _positronPlotService: IPositronPlotsService, @IPositronIPyWidgetsService private readonly _positronIPyWidgetsService: IPositronIPyWidgetsService, @ILogService private readonly _logService: ILogService, - @INotebookService private readonly _notebookService: INotebookService + @INotebookService private readonly _notebookService: INotebookService, + @IEditorService private readonly _editorService: IEditorService, ) { // TODO@softwarenerd - We needed to find a central place where we could ensure that certain // Positron services were up and running early in the application lifecycle. For now, this @@ -982,6 +994,7 @@ export class MainThreadLanguageRuntime implements MainThreadLanguageRuntimeShape this._languageRuntimeService, this._logService, this._notebookService, + this._editorService, this._proxy ); this._runtimes.set(handle, adapter); diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts index ea8027d13b9..1ebeebfd9ea 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts @@ -727,6 +727,15 @@ export class LanguageRuntimeService extends Disposable implements ILanguageRunti } }); })); + this._register(frontendClient.onDidOpenEditor(event => { + this._onDidReceiveRuntimeEventEmitter.fire({ + runtime_id: runtime.metadata.runtimeId, + event: { + name: FrontendEvent.OpenEditor, + data: event + } + }); + })); this._register(frontendClient.onDidShowMessage(event => { this._onDidReceiveRuntimeEventEmitter.fire({ runtime_id: runtime.metadata.runtimeId, diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts index 85afa9bb9dc..10bb94702ec 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; -import { BusyEvent, FrontendEvent, PositronFrontendComm, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent } from './positronFrontendComm'; +import { BusyEvent, FrontendEvent, OpenEditorEvent, PositronFrontendComm, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent } from './positronFrontendComm'; /** @@ -62,6 +62,7 @@ export class FrontEndClientInstance extends Disposable { /** Emitters for events forwarded from the frontend comm */ onDidBusy: Event; + onDidOpenEditor: Event; onDidShowMessage: Event; onDidPromptState: Event; onDidWorkingDirectory: Event; @@ -80,6 +81,7 @@ export class FrontEndClientInstance extends Disposable { this._comm = new PositronFrontendComm(this._client); this.onDidBusy = this._comm.onDidBusy; + this.onDidOpenEditor = this._comm.onDidOpenEditor; this.onDidShowMessage = this._comm.onDidShowMessage; this.onDidPromptState = this._comm.onDidPromptState; this.onDidWorkingDirectory = this._comm.onDidWorkingDirectory; diff --git a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts index 53178e69b16..6d5f3756871 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts @@ -35,6 +35,27 @@ export interface BusyEvent { } +/** + * Event: Open an editor + */ +export interface OpenEditorEvent { + /** + * The path of the file to open + */ + file: string; + + /** + * The line number to jump to + */ + line: number; + + /** + * The column number to jump to + */ + column: number; + +} + /** * Event: Show a message */ @@ -75,6 +96,7 @@ export interface WorkingDirectoryEvent { export enum FrontendEvent { Busy = 'busy', + OpenEditor = 'open_editor', ShowMessage = 'show_message', PromptState = 'prompt_state', WorkingDirectory = 'working_directory' @@ -84,6 +106,7 @@ export class PositronFrontendComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); this.onDidBusy = super.createEventEmitter('busy', ['busy']); + this.onDidOpenEditor = super.createEventEmitter('open_editor', ['file', 'line', 'column']); this.onDidShowMessage = super.createEventEmitter('show_message', ['message']); this.onDidPromptState = super.createEventEmitter('prompt_state', ['input_prompt', 'continuation_prompt']); this.onDidWorkingDirectory = super.createEventEmitter('working_directory', ['directory']); @@ -115,6 +138,12 @@ export class PositronFrontendComm extends PositronBaseComm { * is running. */ onDidBusy: Event; + /** + * Open an editor + * + * This event is used to open an editor with a given file and selection. + */ + onDidOpenEditor: Event; /** * Show a message * From 33f924426dac225883b168f626561b7c37b8b0d2 Mon Sep 17 00:00:00 2001 From: seem Date: Wed, 27 Dec 2023 18:42:21 +0200 Subject: [PATCH 67/71] Add the `clear_console` frontend event The `clear_console` event lets language runtimes instruct Positron to clear the console. This will be used in the Python runtime to correctly support the `clear` magic command. Partially addresses #800. --- positron/comms/frontend-frontend-openrpc.json | 6 ++++++ .../languageRuntime/common/languageRuntime.ts | 9 +++++++++ .../common/languageRuntimeFrontEndClient.ts | 4 +++- .../languageRuntime/common/positronFrontendComm.ts | 14 ++++++++++++++ .../browser/positronConsoleService.ts | 8 ++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/positron/comms/frontend-frontend-openrpc.json b/positron/comms/frontend-frontend-openrpc.json index fe06b2b81ed..bf628ca6b6b 100644 --- a/positron/comms/frontend-frontend-openrpc.json +++ b/positron/comms/frontend-frontend-openrpc.json @@ -19,6 +19,12 @@ } ] }, + { + "name": "clear_console", + "summary": "Clear the console", + "description": "Use this to clear the console.", + "params": [] + }, { "name": "open_editor", "summary": "Open an editor", diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts index 1ebeebfd9ea..40fa9e2e143 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntime.ts @@ -727,6 +727,15 @@ export class LanguageRuntimeService extends Disposable implements ILanguageRunti } }); })); + this._register(frontendClient.onDidClearConsole(event => { + this._onDidReceiveRuntimeEventEmitter.fire({ + runtime_id: runtime.metadata.runtimeId, + event: { + name: FrontendEvent.ClearConsole, + data: event + } + }); + })); this._register(frontendClient.onDidOpenEditor(event => { this._onDidReceiveRuntimeEventEmitter.fire({ runtime_id: runtime.metadata.runtimeId, diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts index 10bb94702ec..caf817e2b25 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntimeFrontEndClient.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; -import { BusyEvent, FrontendEvent, OpenEditorEvent, PositronFrontendComm, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent } from './positronFrontendComm'; +import { BusyEvent, ClearConsoleEvent, FrontendEvent, OpenEditorEvent, PositronFrontendComm, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent } from './positronFrontendComm'; /** @@ -62,6 +62,7 @@ export class FrontEndClientInstance extends Disposable { /** Emitters for events forwarded from the frontend comm */ onDidBusy: Event; + onDidClearConsole: Event; onDidOpenEditor: Event; onDidShowMessage: Event; onDidPromptState: Event; @@ -81,6 +82,7 @@ export class FrontEndClientInstance extends Disposable { this._comm = new PositronFrontendComm(this._client); this.onDidBusy = this._comm.onDidBusy; + this.onDidClearConsole = this._comm.onDidClearConsole; this.onDidOpenEditor = this._comm.onDidOpenEditor; this.onDidShowMessage = this._comm.onDidShowMessage; this.onDidPromptState = this._comm.onDidPromptState; diff --git a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts index 6d5f3756871..c5ed8b111c5 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts @@ -35,6 +35,12 @@ export interface BusyEvent { } +/** + * Event: Clear the console + */ +export interface ClearConsoleEvent { +} + /** * Event: Open an editor */ @@ -96,6 +102,7 @@ export interface WorkingDirectoryEvent { export enum FrontendEvent { Busy = 'busy', + ClearConsole = 'clear_console', OpenEditor = 'open_editor', ShowMessage = 'show_message', PromptState = 'prompt_state', @@ -106,6 +113,7 @@ export class PositronFrontendComm extends PositronBaseComm { constructor(instance: IRuntimeClientInstance) { super(instance); this.onDidBusy = super.createEventEmitter('busy', ['busy']); + this.onDidClearConsole = super.createEventEmitter('clear_console', []); this.onDidOpenEditor = super.createEventEmitter('open_editor', ['file', 'line', 'column']); this.onDidShowMessage = super.createEventEmitter('show_message', ['message']); this.onDidPromptState = super.createEventEmitter('prompt_state', ['input_prompt', 'continuation_prompt']); @@ -138,6 +146,12 @@ export class PositronFrontendComm extends PositronBaseComm { * is running. */ onDidBusy: Event; + /** + * Clear the console + * + * Use this to clear the console. + */ + onDidClearConsole: Event; /** * Open an editor * diff --git a/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts b/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts index 53dffb4f25a..3895f065fde 100644 --- a/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts +++ b/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts @@ -40,6 +40,7 @@ import { ActivityItemInput, ActivityItemInputState } from 'vs/workbench/services import { ActivityItemErrorStream, ActivityItemOutputStream } from 'vs/workbench/services/positronConsole/browser/classes/activityItemStream'; import { IPositronConsoleInstance, IPositronConsoleService, POSITRON_CONSOLE_VIEW_ID, PositronConsoleState } from 'vs/workbench/services/positronConsole/browser/interfaces/positronConsoleService'; import { formatLanguageRuntime, ILanguageRuntime, ILanguageRuntimeExit, ILanguageRuntimeMessage, ILanguageRuntimeService, RuntimeCodeExecutionMode, RuntimeCodeFragmentStatus, RuntimeErrorBehavior, RuntimeExitReason, RuntimeOnlineState, RuntimeState } from 'vs/workbench/services/languageRuntime/common/languageRuntimeService'; +import { FrontendEvent } from 'vs/workbench/services/languageRuntime/common/positronFrontendComm'; /** * The onDidChangeRuntimeItems throttle threshold and throttle interval. The throttle threshold @@ -1515,6 +1516,13 @@ class PositronConsoleInstance extends Disposable implements IPositronConsoleInst } })); + // Add the onDidReceiveRuntimeClientEvent event handler. + this._runtimeDisposableStore.add(this._runtime.onDidReceiveRuntimeClientEvent((event) => { + if (event.name === FrontendEvent.ClearConsole) { + this.clearConsole(); + } + })); + this._runtimeDisposableStore.add(this._runtime.onDidEndSession((exit) => { // If trace is enabled, add a trace runtime item. if (this._trace) { From 3ea9333b3a930115dc380bec06ab0ab99cc55167 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Tue, 2 Jan 2024 10:36:16 +0100 Subject: [PATCH 68/71] Rename plural `Params` to singular `Param` --- positron/comms/frontend-backend-openrpc.json | 1 + .../services/languageRuntime/common/positronFrontendComm.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/positron/comms/frontend-backend-openrpc.json b/positron/comms/frontend-backend-openrpc.json index 1368b6ae4da..73dad97ed96 100644 --- a/positron/comms/frontend-backend-openrpc.json +++ b/positron/comms/frontend-backend-openrpc.json @@ -23,6 +23,7 @@ "schema": { "type": "array", "items": { + "name": "param", "type": "object", "properties": {}, "additionalProperties": true diff --git a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts index c5ed8b111c5..ccc6ea88cd1 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts @@ -13,7 +13,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co /** * Items in Params */ -export interface Params { +export interface Param { [k: string]: unknown; } @@ -132,7 +132,7 @@ export class PositronFrontendComm extends PositronBaseComm { * * @returns The method result */ - callMethod(method: string, params: Array): Promise { + callMethod(method: string, params: Array): Promise { return super.performRpc('call_method', ['method', 'params'], [method, params]); } From 8f44f99644116b2ffe559edf05f3d90f0af286c1 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Tue, 2 Jan 2024 13:20:08 +0100 Subject: [PATCH 69/71] Remove `msg_type` field in RPC requests --- extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts b/extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts index 1302ebdedee..b22d58d169b 100644 --- a/extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts +++ b/extensions/jupyter-adapter/src/LanguageRuntimeAdapter.ts @@ -121,7 +121,6 @@ export class LanguageRuntimeAdapter // NOTE: Currently using nested RPC messages for convenience but // we'd like to do better const request = { - msg_type: 'rpc_request', jsonrpc: '2.0', method: 'call_method', params: { From 0eff50d742c3438e24b3235e48200cac988596fb Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Tue, 2 Jan 2024 13:23:22 +0100 Subject: [PATCH 70/71] Update year in generated comm files --- .../services/languageRuntime/common/positronDataToolComm.ts | 2 +- .../services/languageRuntime/common/positronFrontendComm.ts | 2 +- .../services/languageRuntime/common/positronHelpComm.ts | 2 +- .../services/languageRuntime/common/positronPlotComm.ts | 2 +- .../services/languageRuntime/common/positronVariablesComm.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts index f12006979f8..727b7b54dc3 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataToolComm.ts @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ // diff --git a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts index ccc6ea88cd1..209d717cef7 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronFrontendComm.ts @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ // diff --git a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts index d1506decc35..6b080ce8eeb 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronHelpComm.ts @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ // diff --git a/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts b/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts index 5634f07ecc0..d513a5e2b33 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronPlotComm.ts @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ // diff --git a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts index a4a2e7251d6..d175b82cf4f 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronVariablesComm.ts @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ // From 4d89be9d07c7787400883c1113c4b144d0d35347 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Tue, 2 Jan 2024 14:46:33 +0100 Subject: [PATCH 71/71] Fix ESLint warnings --- positron/comms/generate-comms.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/positron/comms/generate-comms.ts b/positron/comms/generate-comms.ts index 747965dca7a..edd977d2b20 100644 --- a/positron/comms/generate-comms.ts +++ b/positron/comms/generate-comms.ts @@ -915,7 +915,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co } if (frontend) { - let events: string[] = []; + const events: string[] = []; for (const method of frontend.methods) { // Ignore methods that have a result; we're generating event types here @@ -925,7 +925,7 @@ import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/co // Collect enum fields const sentenceName = snakeCaseToSentenceCase(method.name); - events.push(`\t${sentenceName} = '${method.name}'`) + events.push(`\t${sentenceName} = '${method.name}'`); yield '/**\n'; yield formatComment(' * ', `Event: ${method.summary}`);