diff --git a/build/.cachesalt b/build/.cachesalt index af13febe516..58a1efc1632 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2024-08-08T03:47:49.879Z +2024-08-14T18:12:43.548Z diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index 92fe6eb715e..c5f9dfb0036 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -7,6 +7,9 @@ parameters: type: boolean - name: VSCODE_RUN_SMOKE_TESTS type: boolean + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - script: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" @@ -17,34 +20,56 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test.sh --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - script: yarn test-node - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - script: yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: Run unit tests (Browser, Chromium & Webkit) - timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-esm.sh --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - script: yarn test-browser-esm-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium & Webkit) [ESM] + timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test.sh --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - script: yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - script: yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - script: yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - script: yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: Run unit tests (Browser, Chromium & Webkit) - timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-esm.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm --build + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - script: yarn test-browser-esm-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium & Webkit) [ESM] + timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - script: yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - script: yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - script: | @@ -69,24 +94,44 @@ steps: displayName: Build integration tests - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test-integration.sh --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-integration-esm.sh --tfs "Integration Tests" + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test-integration --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + ./scripts/test-integration-esm.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - script: ./scripts/test-web-integration.sh --browser webkit env: diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 734a15c82ba..ccb6b5b35e7 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -9,6 +9,9 @@ parameters: type: boolean - name: VSCODE_RUN_SMOKE_TESTS type: boolean + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -174,6 +177,7 @@ steps: VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} + VSCODE_BUILD_ESM: ${{ parameters.VSCODE_BUILD_ESM }} - ${{ elseif and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - task: DownloadPipelineArtifact@2 diff --git a/build/azure-pipelines/linux/product-build-linux-legacy-server.yml b/build/azure-pipelines/linux/product-build-linux-legacy-server.yml index 26f02657e10..b3b505c1369 100644 --- a/build/azure-pipelines/linux/product-build-linux-legacy-server.yml +++ b/build/azure-pipelines/linux/product-build-linux-legacy-server.yml @@ -5,6 +5,9 @@ parameters: type: boolean - name: VSCODE_ARCH type: string + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - task: NodeTool@0 @@ -201,6 +204,7 @@ steps: VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: false + VSCODE_BUILD_ESM: ${{ parameters.VSCODE_BUILD_ESM }} ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index 91cc411dd44..0b358a81914 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -10,6 +10,9 @@ parameters: - name: PUBLISH_TASK_NAME type: string default: PublishPipelineArtifact@0 + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - script: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" @@ -33,36 +36,61 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test.sh --tfs "Unit Tests" - env: - DISPLAY: ":10" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-esm.sh --tfs "Unit Tests" + env: + DISPLAY: ":10" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - script: yarn test-browser-esm-no-install --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium) [ESM] + timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test.sh --tfs "Unit Tests" + env: + DISPLAY: ":10" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - script: yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - script: yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 - - script: yarn test-node - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - script: yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - script: yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - script: yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-esm.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm --build + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - script: yarn test-browser-esm-no-install --build --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium) [ESM] + timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - script: yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - script: yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - script: | @@ -88,11 +116,18 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test-integration.sh --tfs "Integration Tests" - env: - DISPLAY: ":10" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-integration-esm.sh --tfs "Integration Tests" + env: + DISPLAY: ":10" + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test-integration.sh --tfs "Integration Tests" + env: + DISPLAY: ":10" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - script: ./scripts/test-web-integration.sh --browser chromium displayName: Run integration tests (Browser, Chromium) @@ -103,20 +138,36 @@ steps: timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + ./scripts/test-integration-esm.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - script: ./scripts/test-web-integration.sh --browser chromium env: diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index d1d6bdb9191..2aa304e4fba 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -11,6 +11,9 @@ parameters: type: boolean - name: VSCODE_ARCH type: string + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -267,6 +270,7 @@ steps: VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} + VSCODE_BUILD_ESM: ${{ parameters.VSCODE_BUILD_ESM }} ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index b23a362f9a8..b0e495b7b73 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -100,7 +100,7 @@ parameters: displayName: "Skip tests" type: boolean default: false - - name: VSCODE_BUILD_ESM # TODO@bpasero remove me once ESM is shipped + - name: VSCODE_BUILD_ESM # TODO@bpasero TODO@esm remove me once ESM is shipped displayName: "️❗ Build as ESM (!FOR TESTING ONLY!) ️❗" type: boolean default: false @@ -175,7 +175,7 @@ resources: ref: refs/tags/release extends: - template: v1/1ES.Unofficial.PipelineTemplate.yml@1esPipelines + template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines parameters: sdl: tsa: @@ -190,9 +190,10 @@ extends: validateToolOutput: None allTools: true codeql: + runSourceLanguagesInSourceAnalysis: true compiled: enabled: false - runSourceLanguagesInSourceAnalysis: true + justificationForDisabling: "CodeQL breaks ESRP CodeSign on macOS (ICM #520035761, githubcustomers/microsoft-codeql-support#198)" credscan: suppressionsFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/CredScanSuppressions.json eslint: @@ -361,6 +362,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: true @@ -375,6 +377,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false @@ -389,6 +392,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false @@ -404,6 +408,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} @@ -427,6 +432,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: arm64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false @@ -455,6 +461,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: true VSCODE_RUN_INTEGRATION_TESTS: false @@ -470,6 +477,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: true @@ -485,6 +493,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -502,6 +511,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} @@ -527,6 +537,7 @@ extends: parameters: VSCODE_ARCH: armhf VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -542,6 +553,7 @@ extends: parameters: VSCODE_ARCH: arm64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -566,6 +578,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF_LEGACY_SERVER, true) }}: @@ -578,6 +591,7 @@ extends: parameters: VSCODE_ARCH: armhf VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_RUN_INTEGRATION_TESTS: false - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64_LEGACY_SERVER, true) }}: @@ -590,6 +604,7 @@ extends: parameters: VSCODE_ARCH: arm64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_RUN_INTEGRATION_TESTS: false - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_ALPINE'], true)) }}: @@ -642,6 +657,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: true VSCODE_RUN_INTEGRATION_TESTS: false @@ -655,6 +671,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: true @@ -668,6 +685,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -682,6 +700,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -696,6 +715,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} @@ -727,6 +747,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index c9b2d6cfa61..fb3f3f4d73b 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -12,6 +12,9 @@ parameters: - name: PUBLISH_TASK_NAME type: string default: PublishPipelineArtifact@0 + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - powershell: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" @@ -22,30 +25,48 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: .\scripts\test.bat --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - powershell: yarn test-node - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - powershell: node test/unit/browser/index.js --sequential --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - powershell: .\scripts\test-esm.bat --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - powershell: yarn test-node-esm + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - powershell: node test/unit/browser/index.esm.js --sequential --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - powershell: .\scripts\test.bat --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - powershell: yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - powershell: node test/unit/browser/index.js --sequential --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: .\scripts\test.bat --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - powershell: yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - powershell: yarn test-browser-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - powershell: .\scripts\test-esm.bat --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm --build + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - powershell: yarn test-browser-esm-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - powershell: .\scripts\test.bat --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - powershell: yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - powershell: yarn test-browser-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - powershell: | @@ -77,9 +98,14 @@ steps: condition: succeededOrFailed() - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: .\scripts\test-integration.bat --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - powershell: .\scripts\test-integration-esm.bat --tfs "Integration Tests" + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - powershell: .\scripts\test-integration.bat --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - powershell: .\scripts\test-web-integration.bat --browser firefox displayName: Run integration tests (Browser, Firefox) @@ -90,20 +116,36 @@ steps: timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)" - exec { .\scripts\test-integration.bat --build --tfs "Integration Tests" } - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - powershell: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)" + exec { .\scripts\test-integration-esm.bat --build --tfs "Integration Tests" } + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - powershell: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)" + exec { .\scripts\test-integration.bat --build --tfs "Integration Tests" } + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - powershell: | . build/azure-pipelines/win32/exec.ps1 diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 7fa2df0ddd1..a67e0ccf4e3 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -11,6 +11,9 @@ parameters: type: boolean - name: VSCODE_RUN_SMOKE_TESTS type: boolean + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -179,6 +182,7 @@ steps: VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} + VSCODE_BUILD_ESM: ${{ parameters.VSCODE_BUILD_ESM }} ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 diff --git a/build/gulpfile.js b/build/gulpfile.js index e945c06eed4..785719d3e14 100644 --- a/build/gulpfile.js +++ b/build/gulpfile.js @@ -34,11 +34,15 @@ gulp.task(compileClientTask); const watchClientTask = task.define('watch-client', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), task.parallel(watchTask('out', false), watchApiProposalNamesTask))); gulp.task(watchClientTask); +const watchClientESMTask = task.define('watch-client-esm', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), task.parallel(watchTask('out', false, 'src2'), watchApiProposalNamesTask))); +gulp.task(watchClientESMTask); + // All const _compileTask = task.define('compile', task.parallel(monacoTypecheckTask, compileClientTask, compileExtensionsTask, compileExtensionMediaTask)); gulp.task(_compileTask); gulp.task(task.define('watch', task.parallel(/* monacoTypecheckWatchTask, */ watchClientTask, watchExtensionsTask))); +gulp.task(task.define('watch-esm', task.parallel(/* monacoTypecheckWatchTask, */ watchClientESMTask, watchExtensionsTask))); // Default gulp.task('default', _compileTask); diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 1073837ea7f..f07d722199a 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -54,7 +54,7 @@ const BUILD_TARGETS = [ { platform: 'linux', arch: 'alpine' }, ]; -const serverResources = [ +const serverResourceIncludes = [ // NLS 'out-build/nls.messages.json', @@ -73,19 +73,36 @@ const serverResources = [ 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish', +]; +const serverResourceExcludes = [ + '!out-build/vs/**/{electron-sandbox,electron-main}/**', + '!out-build/vs/editor/standalone/**', + '!out-build/vs/workbench/**/*-tb.png', '!**/test/**' ]; -const serverWithWebResources = [ +const serverResources = [ + ...serverResourceIncludes, + ...serverResourceExcludes +]; - // Include all of server... - ...serverResources, - - // ...and all of web +const serverWithWebResourceIncludes = [ + ...serverResourceIncludes, ...vscodeWebResourceIncludes ]; +const serverWithWebResourceExcludes = [ + ...serverResourceExcludes, + '!out-build/vs/code/**/*-dev.html', + '!out-build/vs/code/**/*-dev.esm.html', +]; + +const serverWithWebResources = [ + ...serverWithWebResourceIncludes, + ...serverWithWebResourceExcludes +]; + const serverEntryPoints = [ { name: 'vs/server/node/server.main', diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 1323931216a..609b0d60b0a 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -56,6 +56,7 @@ const vscodeResources = [ 'out-build/vs/**/*.{svg,png,html,jpg,mp3}', '!out-build/vs/code/browser/**/*.html', '!out-build/vs/code/**/*-dev.html', + '!out-build/vs/code/**/*-dev.esm.html', '!out-build/vs/editor/standalone/**/*.svg', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', 'out-build/vs/base/browser/ui/codicons/codicon/**', @@ -70,8 +71,8 @@ const vscodeResources = [ 'out-build/vs/workbench/contrib/terminal/browser/media/*.sh', 'out-build/vs/workbench/contrib/terminal/browser/media/*.zsh', 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', - '!out-build/vs/workbench/contrib/issue/browser/*.html', '!out-build/vs/workbench/contrib/issue/**/*-dev.html', + '!out-build/vs/workbench/contrib/issue/**/*-dev.esm.html', 'out-build/vs/**/markdown.css', 'out-build/vs/workbench/contrib/tasks/**/*.json', '!**/test/**' diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index 0424b12fa45..190dac79372 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -47,6 +47,7 @@ const vscodeWebResourceIncludes = [ // Extension Worker 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', + 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html', // Web node paths (needed for integration tests) 'out-build/vs/webPackagePaths.js', @@ -62,6 +63,8 @@ const vscodeWebResources = [ '!out-build/vs/**/{node,electron-sandbox,electron-main}/**', '!out-build/vs/editor/standalone/**', '!out-build/vs/workbench/**/*-tb.png', + '!out-build/vs/code/**/*-dev.html', + '!out-build/vs/code/**/*-dev.esm.html', '!**/test/**' ]; diff --git a/build/lib/compilation.js b/build/lib/compilation.js index cafca34a0d8..e6fe4b592a6 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -139,11 +139,11 @@ function compileTask(src, out, build, options = {}) { task.taskName = `compile-${path.basename(src)}`; return task; } -function watchTask(out, build) { +function watchTask(out, build, srcPath = 'src') { const task = () => { - const compile = createCompile('src', { build, emitError: false, transpileOnly: false, preserveEnglish: false }); - const src = gulp.src('src/**', { base: 'src' }); - const watchSrc = watch('src/**', { base: 'src', readDelay: 200 }); + const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false }); + const src = gulp.src(`${srcPath}/**`, { base: srcPath }); + const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 }); const generator = new MonacoGenerator(true); generator.execute(); return watchSrc diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 8c3614b4c13..978fb15df9e 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -170,13 +170,13 @@ export function compileTask(src: string, out: string, build: boolean, options: { return task; } -export function watchTask(out: string, build: boolean): task.StreamTask { +export function watchTask(out: string, build: boolean, srcPath: string = 'src'): task.StreamTask { const task = () => { - const compile = createCompile('src', { build, emitError: false, transpileOnly: false, preserveEnglish: false }); + const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false }); - const src = gulp.src('src/**', { base: 'src' }); - const watchSrc = watch('src/**', { base: 'src', readDelay: 200 }); + const src = gulp.src(`${srcPath}/**`, { base: srcPath }); + const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 }); const generator = new MonacoGenerator(true); generator.execute(); diff --git a/build/lib/standalone.js b/build/lib/standalone.js index cf0e452aff3..78030842569 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -131,7 +131,7 @@ function createESMSourcesAndResources2(options) { } if (file === 'tsconfig.json') { const tsConfig = JSON.parse(fs.readFileSync(path.join(SRC_FOLDER, file)).toString()); - tsConfig.compilerOptions.module = 'es6'; + tsConfig.compilerOptions.module = 'es2022'; tsConfig.compilerOptions.outDir = path.join(path.relative(OUT_FOLDER, OUT_RESOURCES_FOLDER), 'vs').replace(/\\/g, '/'); write(getDestAbsoluteFilePath(file), JSON.stringify(tsConfig, null, '\t')); continue; diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 9a65bfa7444..e1b9db65e12 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -157,7 +157,7 @@ export function createESMSourcesAndResources2(options: IOptions2): void { if (file === 'tsconfig.json') { const tsConfig = JSON.parse(fs.readFileSync(path.join(SRC_FOLDER, file)).toString()); - tsConfig.compilerOptions.module = 'es6'; + tsConfig.compilerOptions.module = 'es2022'; tsConfig.compilerOptions.outDir = path.join(path.relative(OUT_FOLDER, OUT_RESOURCES_FOLDER), 'vs').replace(/\\/g, '/'); write(getDestAbsoluteFilePath(file), JSON.stringify(tsConfig, null, '\t')); continue; diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index d45d5bc8cbc..9ca239e3253 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -81,11 +81,6 @@ for (let dir of dirs) { } } - if (/^(.build\/distro\/npm\/)?remote/.test(dir) && process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64')) { - // windows arm: do not execute `yarn` on remote folder - continue; - } - let opts; if (dir === 'build') { diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 7c51e83ff9c..f359215ec8e 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -122,19 +122,7 @@ function installHeaders() { cp.execFileSync(node_gyp, ['install', '--dist-url', local.disturl, local.target], { shell: true }); } - // Avoid downloading headers for Windows arm64 till we move to Nodejs v19 in remote - // which is the first official release with support for the architecture. Downloading - // the headers for older versions now redirect to https://origin.nodejs.org/404.html - // which causes checksum validation error in node-gyp. - // - // gyp http 200 https://origin.nodejs.org/404.html - // gyp WARN install got an error, rolling back install - // gyp ERR! install error - // gyp ERR! stack Error: win-arm64/node.lib local checksum 4c62bed7a032f7b36984321b7ffdd60b596fac870672037ff879ae9ac9548fb7 not match remote undefined - // - if (remote !== undefined && !versions.has(remote.target) && - process.env['npm_config_arch'] !== "arm64" && - process.arch !== "arm64") { + if (remote !== undefined && !versions.has(remote.target)) { // Both disturl and target come from a file checked into our repository cp.execFileSync(node_gyp, ['install', '--dist-url', remote.disturl, remote.target], { shell: true }); } diff --git a/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts b/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts index c26476a983d..d639b7fe999 100644 --- a/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts +++ b/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts @@ -7,7 +7,6 @@ import { PackageManager } from '@vscode/ts-package-manager'; import { basename, join } from 'path'; import * as vscode from 'vscode'; import { URI } from 'vscode-uri'; -import { Throttler } from '../utils/async'; import { Disposable } from '../utils/dispose'; import { MemFs } from './memFs'; import { Logger } from '../logging/logger'; @@ -19,9 +18,7 @@ export class AutoInstallerFs extends Disposable implements vscode.FileSystemProv private readonly memfs: MemFs; private readonly packageManager: PackageManager; - private readonly _projectCache = new Map(); + private readonly _projectCache = new Map | undefined>(); private readonly _emitter = this._register(new vscode.EventEmitter()); readonly onDidChangeFile = this._emitter.event; @@ -78,9 +75,8 @@ export class AutoInstallerFs extends Disposable implements vscode.FileSystemProv } watch(resource: vscode.Uri): vscode.Disposable { - const mapped = URI.file(new MappedUri(resource).path); - this.logger.trace(`AutoInstallerFs.watch. Original: ${resource.toString()}, Mapped: ${mapped.toString()}`); - return this.memfs.watch(mapped); + this.logger.trace(`AutoInstallerFs.watch. Resource: ${resource.toString()}}`); + return this.memfs.watch(resource); } async stat(uri: vscode.Uri): Promise { @@ -151,20 +147,20 @@ export class AutoInstallerFs extends Disposable implements vscode.FileSystemProv throw vscode.FileSystemError.FileNotFound(); } - const root = this.getProjectRoot(incomingUri.path); + const root = await this.getProjectRoot(incomingUri.original); if (!root) { return; } this.logger.trace(`AutoInstallerFs.ensurePackageContents. Path: ${incomingUri.path}, Root: ${root}`); - let projectEntry = this._projectCache.get(root); - if (!projectEntry) { - projectEntry = { throttler: new Throttler() }; - this._projectCache.set(root, projectEntry); + const existingInstall = this._projectCache.get(root); + if (existingInstall) { + this.logger.trace(`AutoInstallerFs.ensurePackageContents. Found ongoing install for: ${root}/node_modules`); + return existingInstall; } - projectEntry.throttler.queue(async () => { + const installing = (async () => { const proj = await this.packageManager.resolveProject(root, await this.getInstallOpts(incomingUri.original, root)); try { await proj.restore(); @@ -172,15 +168,16 @@ export class AutoInstallerFs extends Disposable implements vscode.FileSystemProv console.error(`failed to restore package at ${incomingUri.path}: `, e); throw e; } - }); + })(); + this._projectCache.set(root, installing); + await installing; } private async getInstallOpts(originalUri: URI, root: string) { const vsfs = vscode.workspace.fs; - let pkgJson; - try { - pkgJson = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package.json') }))); - } catch (e) { } + + // We definitely need a package.json to be there. + const pkgJson = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package.json') }))); let kdlLock; try { @@ -199,9 +196,19 @@ export class AutoInstallerFs extends Disposable implements vscode.FileSystemProv }; } - private getProjectRoot(path: string): string | undefined { - const pkgPath = path.match(/(^.*)\/node_modules/); - return pkgPath?.[1]; + private async getProjectRoot(incomingUri: URI): Promise { + const vsfs = vscode.workspace.fs; + const pkgPath = incomingUri.path.match(/^(.*?)\/node_modules/); + const ret = pkgPath?.[1]; + if (!ret) { + return; + } + try { + await vsfs.stat(incomingUri.with({ path: join(ret, 'package.json') })); + return ret; + } catch (e) { + return; + } } } @@ -214,7 +221,7 @@ class MappedUri { const parts = uri.path.match(/^\/([^\/]+)\/([^\/]*)(?:\/(.+))?$/); if (!parts) { - throw new Error(`Invalid path: ${uri.path}`); + throw new Error(`Invalid uri: ${uri.toString()}, ${uri.path}`); } const scheme = parts[1]; diff --git a/extensions/typescript-language-features/web/src/fileWatcherManager.ts b/extensions/typescript-language-features/web/src/fileWatcherManager.ts index 5bbce244688..6ae4472e503 100644 --- a/extensions/typescript-language-features/web/src/fileWatcherManager.ts +++ b/extensions/typescript-language-features/web/src/fileWatcherManager.ts @@ -53,9 +53,9 @@ export class FileWatcherManager { this.watchFiles.set(path, { callback, pollingInterval, options }); const watchIds = [++this.watchId]; this.watchPort.postMessage({ type: 'watchFile', uri: uri, id: watchIds[0] }); - if (this.enabledExperimentalTypeAcquisition && looksLikeNodeModules(path)) { + if (this.enabledExperimentalTypeAcquisition && looksLikeNodeModules(path) && uri.scheme !== 'vscode-global-typings') { watchIds.push(++this.watchId); - this.watchPort.postMessage({ type: 'watchFile', uri: mapUri(uri, 'vscode-node-modules'), id: watchIds[1] }); + this.watchPort.postMessage({ type: 'watchFile', uri: mapUri(uri, 'vscode-global-typings'), id: watchIds[1] }); } return { close: () => { diff --git a/extensions/typescript-language-features/web/src/typingsInstaller/typingsInstaller.ts b/extensions/typescript-language-features/web/src/typingsInstaller/typingsInstaller.ts index 7c40993d6df..1f7790dc783 100644 --- a/extensions/typescript-language-features/web/src/typingsInstaller/typingsInstaller.ts +++ b/extensions/typescript-language-features/web/src/typingsInstaller/typingsInstaller.ts @@ -70,10 +70,12 @@ export class WebTypingsInstallerClient implements ts.server.ITypingsInstaller { break; case 'event::beginInstallTypes': case 'event::endInstallTypes': + // TODO(@zkat): maybe do something with this? + case 'action::watchTypingLocations': // Don't care. break; default: - throw new Error(`unexpected response: ${response}`); + throw new Error(`unexpected response: ${JSON.stringify(response)}`); } } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts index f9d8d6a82db..a067ba65a92 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts @@ -18,6 +18,10 @@ async function openRandomNotebookDocument() { return vscode.workspace.openNotebookDocument(uri); } +async function openUntitledNotebookDocument(data?: vscode.NotebookData) { + return vscode.workspace.openNotebookDocument('notebookCoreTest', data); +} + export async function saveAllFilesAndCloseAll() { await saveAllEditors(); await closeAllEditors(); @@ -188,6 +192,27 @@ const apiTestSerializer: vscode.NotebookSerializer = { assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); }); + test('Opening an utitled notebook without content will only open the editor when shown.', async function () { + const document = await openUntitledNotebookDocument(); + + assert.strictEqual(vscode.window.activeNotebookEditor, undefined); + + // opening a cell-uri opens a notebook editor + await vscode.window.showNotebookDocument(document); + + assert.strictEqual(!!vscode.window.activeNotebookEditor, true); + assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); + }); + + test('Opening an untitled notebook with content will open a dirty document.', async function () { + const language = 'python'; + const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', language); + const data = new vscode.NotebookData([cell]); + const doc = await vscode.workspace.openNotebookDocument('jupyter-notebook', data); + + assert.strictEqual(doc.isDirty, true); + }); + test('Cannot open notebook from cell-uri with vscode.open-command', async function () { const document = await openRandomNotebookDocument(); diff --git a/migrate.mjs b/migrate.mjs index aa58c726bad..1d559c81084 100644 --- a/migrate.mjs +++ b/migrate.mjs @@ -31,7 +31,7 @@ const binaryFileExtensions = new Set([ function migrate() { console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`); - console.log(`STARTING MIGRATION of src to src2.`); + console.log(`STARTING AMD->ESM MIGRATION of src to src2.`); // installing watcher quickly to avoid missing early events const watchSrc = enableWatching ? watch('src/**', { base: 'src', readDelay: 200 }) : undefined; @@ -49,7 +49,8 @@ function migrate() { writeFileSync(join(dstFolder, '.gitignore'), `*`); console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`); - console.log(`COMPLETED MIGRATION of src to src2. You can now launch yarn watch or yarn watch-client`); + console.log(`COMPLETED AMD->ESM MIGRATION of src to src2. You can now launch yarn watch-esm or yarn watch-client-esm`); + console.log(`Make sure to set the environment variable VSCODE_BUILD_ESM to a string of value 'true' if you want to build VS Code`); if (watchSrc) { console.log(`WATCHING src for changes...`); diff --git a/package.json b/package.json index 945f22deea7..c92bb80df86 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,17 @@ "scripts": { "test": "echo Please run any of the test scripts from the scripts folder.", "test-browser": "npx playwright install && node test/unit/browser/index.js", + "test-browser-esm": "npx playwright install && node test/unit/browser/index.esm.js", "test-browser-no-install": "node test/unit/browser/index.js", + "test-browser-esm-no-install": "node test/unit/browser/index.esm.js", "test-node": "mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit", + "test-node-esm": "mocha test/unit/node/index.mjs --delay --ui=tdd --timeout=5000 --exit", "test-extension": "vscode-test", "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", "compile": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js compile", "watch": "npm-run-all -lp watch-client watch-extensions", + "watch-esm": "npm-run-all -lp watch-client-esm watch-extensions", "watchd": "deemon yarn watch", "watch-webd": "deemon yarn watch-web", "kill-watchd": "deemon --kill yarn watch", @@ -25,6 +29,7 @@ "restart-watchd": "deemon --restart yarn watch", "restart-watch-webd": "deemon --restart yarn watch-web", "watch-client": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-client", + "watch-client-esm": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-client-esm", "watch-clientd": "deemon yarn watch-client", "kill-watch-clientd": "deemon --kill yarn watch-client", "watch-extensions": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media", @@ -105,7 +110,7 @@ "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.0.0", + "vscode-textmate": "9.1.0", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index aa24bf82338..3105d90868f 100644 --- a/remote/package.json +++ b/remote/package.json @@ -34,7 +34,7 @@ "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.0.0", + "vscode-textmate": "9.1.0", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index c7fdaf96b33..6d2877d9893 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -18,6 +18,6 @@ "jschardet": "3.1.3", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", - "vscode-textmate": "9.0.0" + "vscode-textmate": "9.1.0" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index cdc4d7918b8..cf46e56d26f 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -110,7 +110,7 @@ vscode-oniguruma@1.7.0: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== -vscode-textmate@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" - integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== +vscode-textmate@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.1.0.tgz#656a6aa163a9578397ba810733952bedb2b47202" + integrity sha512-lxKSVp2DkFOx9RDAvpiYUrB9/KT1fAfi1aE8CBGstP8N7rLF+Seifj8kDA198X0mYj1CjQUC+81+nQf8CO0nVA== diff --git a/remote/yarn.lock b/remote/yarn.lock index 2478846e5ff..a3af51c0e36 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -688,10 +688,10 @@ vscode-regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-textmate@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" - integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== +vscode-textmate@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.1.0.tgz#656a6aa163a9578397ba810733952bedb2b47202" + integrity sha512-lxKSVp2DkFOx9RDAvpiYUrB9/KT1fAfi1aE8CBGstP8N7rLF+Seifj8kDA198X0mYj1CjQUC+81+nQf8CO0nVA== wrappy@1: version "1.0.2" diff --git a/scripts/code-web.js b/scripts/code-web.js index 467f147d027..42fa65aaa4c 100644 --- a/scripts/code-web.js +++ b/scripts/code-web.js @@ -74,6 +74,10 @@ async function main() { openSystemBrowser = true; } + if (fs.existsSync(path.join(APP_ROOT, 'src2')) || fs.existsSync(path.join(APP_ROOT, 'out-build', 'esm'))) { + serverArgs.push('--esm'); + } + serverArgs.push('--sourcesPath', APP_ROOT); serverArgs.push(...process.argv.slice(2).filter(v => !v.startsWith('--playground') && v !== '--no-playground')); diff --git a/scripts/test-esm.bat b/scripts/test-esm.bat new file mode 100644 index 00000000000..faeff79007b --- /dev/null +++ b/scripts/test-esm.bat @@ -0,0 +1,31 @@ +@echo off +setlocal + +set ELECTRON_RUN_AS_NODE= + +pushd %~dp0\.. + +:: Get Code.exe location +for /f "tokens=2 delims=:," %%a in ('findstr /R /C:"\"nameShort\":.*" product.json') do set NAMESHORT=%%~a +set NAMESHORT=%NAMESHORT: "=% +set NAMESHORT=%NAMESHORT:"=%.exe +set CODE=".build\electron\%NAMESHORT%" + +:: Download Electron if needed +call node build\lib\electron.js +if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron + +:: Run tests +set ELECTRON_ENABLE_LOGGING=1 +%CODE% .\test\unit\electron\index.esm.js --crash-reporter-directory=%~dp0\..\.build\crashes %* + +popd + +endlocal + +:: app.exit(0) is exiting with code 255 in Electron 1.7.4. +:: See https://github.com/microsoft/vscode/issues/28582 +echo errorlevel: %errorlevel% +if %errorlevel% == 255 set errorlevel=0 + +exit /b %errorlevel% diff --git a/scripts/test-esm.sh b/scripts/test-esm.sh new file mode 100755 index 00000000000..ddb619017a6 --- /dev/null +++ b/scripts/test-esm.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -e + +if [[ "$OSTYPE" == "darwin"* ]]; then + realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } + ROOT=$(dirname $(dirname $(realpath "$0"))) +else + ROOT=$(dirname $(dirname $(readlink -f $0))) + # --disable-dev-shm-usage: when run on docker containers where size of /dev/shm + # partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory + LINUX_EXTRA_ARGS="--disable-dev-shm-usage" +fi + +cd $ROOT + +if [[ "$OSTYPE" == "darwin"* ]]; then + NAME=`node -p "require('./product.json').nameLong"` + CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron" +else + NAME=`node -p "require('./product.json').applicationName"` + CODE=".build/electron/$NAME" +fi + +VSCODECRASHDIR=$ROOT/.build/crashes + +# Node modules +test -d node_modules || yarn + +# Get electron +yarn electron + +# Unit Tests +if [[ "$OSTYPE" == "darwin"* ]]; then + cd $ROOT ; ulimit -n 4096 ; \ + ELECTRON_ENABLE_LOGGING=1 \ + "$CODE" \ + test/unit/electron/index.esm.js --crash-reporter-directory=$VSCODECRASHDIR "$@" +else + cd $ROOT ; \ + ELECTRON_ENABLE_LOGGING=1 \ + "$CODE" \ + test/unit/electron/index.esm.js --crash-reporter-directory=$VSCODECRASHDIR $LINUX_EXTRA_ARGS "$@" +fi diff --git a/scripts/test-integration-esm.bat b/scripts/test-integration-esm.bat new file mode 100644 index 00000000000..0d12b225179 --- /dev/null +++ b/scripts/test-integration-esm.bat @@ -0,0 +1,119 @@ +@echo off +setlocal + +pushd %~dp0\.. + +set VSCODEUSERDATADIR=%TEMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,2% +set VSCODECRASHDIR=%~dp0\..\.build\crashes +set VSCODELOGSDIR=%~dp0\..\.build\logs\integration-tests + +:: Figure out which Electron to use for running tests +if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( + chcp 65001 + set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat + set VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE=1 + + echo Running integration tests out of sources. +) else ( + set VSCODE_CLI=1 + set ELECTRON_ENABLE_LOGGING=1 + + echo Running integration tests with '%INTEGRATION_TEST_ELECTRON_PATH%' as build. +) + +echo Storing crash reports into '%VSCODECRASHDIR%'. +echo Storing log files into '%VSCODELOGSDIR%'. + + +:: Tests standalone (AMD) + +echo. +echo ### node.js integration tests +call .\scripts\test-esm.bat --runGlob **\*.integrationTest.js %* +if %errorlevel% neq 0 exit /b %errorlevel% + + +:: Tests in the extension host + +set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% + +echo. +echo ### API tests (folder) +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### API tests (workspace) +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Colorize tests +call yarn test-extension -l vscode-colorize-tests +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### TypeScript tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Markdown tests +call yarn test-extension -l markdown-language-features +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Emmet tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\emmet\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Git tests +for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i +set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% +mkdir %GITWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Ipynb tests +call yarn test-extension -l ipynb +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Notebook Output tests +call yarn test-extension -l notebook-renderers +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Configuration editing tests +set CFWORKSPACE=%TEMPDIR%\cf-%RANDOM% +mkdir %CFWORKSPACE% +call yarn test-extension -l configuration-editing +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### GitHub Authentication tests +call yarn test-extension -l github-authentication +if %errorlevel% neq 0 exit /b %errorlevel% + +:: Tests standalone (CommonJS) + +echo. +echo ### CSS tests +call %~dp0\node-electron.bat %~dp0\..\extensions\css-language-features/server/test/index.js +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### HTML tests +call %~dp0\node-electron.bat %~dp0\..\extensions\html-language-features/server/test/index.js +if %errorlevel% neq 0 exit /b %errorlevel% + + +:: Cleanup + +rmdir /s /q %VSCODEUSERDATADIR% + +popd + +endlocal diff --git a/scripts/test-integration-esm.sh b/scripts/test-integration-esm.sh new file mode 100755 index 00000000000..dde3bc0520d --- /dev/null +++ b/scripts/test-integration-esm.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +set -e + +if [[ "$OSTYPE" == "darwin"* ]]; then + realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } + ROOT=$(dirname $(dirname $(realpath "$0"))) +else + ROOT=$(dirname $(dirname $(readlink -f $0))) + # --disable-dev-shm-usage: when run on docker containers where size of /dev/shm + # partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory + LINUX_EXTRA_ARGS="--disable-dev-shm-usage" +fi + +VSCODEUSERDATADIR=`mktemp -d 2>/dev/null` +VSCODECRASHDIR=$ROOT/.build/crashes +VSCODELOGSDIR=$ROOT/.build/logs/integration-tests + +cd $ROOT + +# Figure out which Electron to use for running tests +if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ] +then + INTEGRATION_TEST_ELECTRON_PATH="./scripts/code.sh" + + echo "Running integration tests out of sources." +else + export VSCODE_CLI=1 + export ELECTRON_ENABLE_LOGGING=1 + + echo "Running integration tests with '$INTEGRATION_TEST_ELECTRON_PATH' as build." +fi + +echo "Storing crash reports into '$VSCODECRASHDIR'." +echo "Storing log files into '$VSCODELOGSDIR'." + + +# Tests standalone (AMD) + +echo +echo "### node.js integration tests" +echo +./scripts/test-esm.sh --runGlob **/*.integrationTest.js "$@" + + +# Tests in the extension host + +API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-extensions --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" + +if [ -z "$INTEGRATION_TEST_APP_NAME" ]; then + kill_app() { true; } +else + kill_app() { killall $INTEGRATION_TEST_APP_NAME || true; } +fi + +echo +echo "### API tests (folder)" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### API tests (workspace)" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### Colorize tests" +echo +yarn test-extension -l vscode-colorize-tests +kill_app + +echo +echo "### TypeScript tests" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test/unit $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### Markdown tests" +echo +yarn test-extension -l markdown-language-features +kill_app + +echo +echo "### Emmet tests" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/emmet/test-workspace --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### Git tests" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### Ipynb tests" +echo +yarn test-extension -l ipynb +kill_app + +echo +echo "### Notebook Output tests" +echo +yarn test-extension -l notebook-renderers +kill_app + +echo +echo "### Configuration editing tests" +echo +yarn test-extension -l configuration-editing +kill_app + +echo +echo "### GitHub Authentication tests" +echo +yarn test-extension -l github-authentication +kill_app + +# Tests standalone (CommonJS) + +echo +echo "### CSS tests" +echo +cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js + +echo +echo "### HTML tests" +echo +cd $ROOT/extensions/html-language-features/server && $ROOT/scripts/node-electron.sh test/index.js + + +# Cleanup + +rm -rf $VSCODEUSERDATADIR diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index c228faccfed..b8cc7a8bb50 100644 --- a/src/bootstrap-amd.js +++ b/src/bootstrap-amd.js @@ -11,20 +11,16 @@ * @import { IProductConfiguration } from './vs/base/common/product' */ -// ESM-comment-begin -const isESM = false; -// ESM-comment-end // ESM-uncomment-begin // import * as path from 'path'; // import * as fs from 'fs'; // import { fileURLToPath } from 'url'; // import { createRequire, register } from 'node:module'; // import { product, pkg } from './bootstrap-meta.js'; -// import * as bootstrapNode from './bootstrap-node.js'; +// import './bootstrap-node.js'; // import * as performance from './vs/base/common/performance.js'; // // const require = createRequire(import.meta.url); -// const isESM = true; // const module = { exports: {} }; // const __dirname = path.dirname(fileURLToPath(import.meta.url)); // @@ -171,77 +167,77 @@ async function doSetupNLS() { //#region Loader Config -if (isESM) { +// ESM-uncomment-begin +// /** +// * @param {string=} entrypoint +// * @param {(value: any) => void} [onLoad] +// * @param {(err: Error) => void} [onError] +// */ +// module.exports.load = function (entrypoint, onLoad, onError) { +// if (!entrypoint) { +// return; +// } - /** - * @param {string=} entrypoint - * @param {(value: any) => void} [onLoad] - * @param {(err: Error) => void} [onError] - */ - module.exports.load = function (entrypoint, onLoad, onError) { - if (!entrypoint) { - return; - } +// entrypoint = `./${entrypoint}.js`; - entrypoint = `./${entrypoint}.js`; +// onLoad = onLoad || function () { }; +// onError = onError || function (err) { console.error(err); }; - onLoad = onLoad || function () { }; - onError = onError || function (err) { console.error(err); }; +// setupNLS().then(() => { +// performance.mark(`code/fork/willLoadCode`); +// import(entrypoint).then(onLoad, onError); +// }); +// }; +// ESM-uncomment-end - setupNLS().then(() => { - performance.mark(`code/fork/willLoadCode`); - import(entrypoint).then(onLoad, onError); - }); - }; -} else { +// ESM-comment-begin +// @ts-ignore +const loader = require('./vs/loader'); - // @ts-ignore - const loader = require('./vs/loader'); +loader.config({ + baseUrl: bootstrapNode.fileUriFromPath(__dirname, { isWindows: process.platform === 'win32' }), + catchError: true, + nodeRequire, + amdModulesPattern: /^vs\//, + recordStats: true +}); - loader.config({ - baseUrl: bootstrapNode.fileUriFromPath(__dirname, { isWindows: process.platform === 'win32' }), - catchError: true, - nodeRequire, - amdModulesPattern: /^vs\//, - recordStats: true +// Running in Electron +if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { + loader.define('fs', ['original-fs'], function (/** @type {import('fs')} */originalFS) { + return originalFS; // replace the patched electron fs with the original node fs for all AMD code }); +} - // Running in Electron - if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { - loader.define('fs', ['original-fs'], function (/** @type {import('fs')} */originalFS) { - return originalFS; // replace the patched electron fs with the original node fs for all AMD code +/** + * @param {string=} entrypoint + * @param {(value: any) => void} [onLoad] + * @param {(err: Error) => void} [onError] + */ +module.exports.load = function (entrypoint, onLoad, onError) { + if (!entrypoint) { + return; + } + + // code cache config + if (process.env['VSCODE_CODE_CACHE_PATH']) { + loader.config({ + nodeCachedData: { + path: process.env['VSCODE_CODE_CACHE_PATH'], + seed: entrypoint + } }); } - /** - * @param {string=} entrypoint - * @param {(value: any) => void} [onLoad] - * @param {(err: Error) => void} [onError] - */ - module.exports.load = function (entrypoint, onLoad, onError) { - if (!entrypoint) { - return; - } + onLoad = onLoad || function () { }; + onError = onError || function (err) { console.error(err); }; - // code cache config - if (process.env['VSCODE_CODE_CACHE_PATH']) { - loader.config({ - nodeCachedData: { - path: process.env['VSCODE_CODE_CACHE_PATH'], - seed: entrypoint - } - }); - } - - onLoad = onLoad || function () { }; - onError = onError || function (err) { console.error(err); }; - - setupNLS().then(() => { - performance.mark('code/fork/willLoadCode'); - loader([entrypoint], onLoad, onError); - }); - }; -} + setupNLS().then(() => { + performance.mark('code/fork/willLoadCode'); + loader([entrypoint], onLoad, onError); + }); +}; +// ESM-comment-end //#endregion diff --git a/src/bootstrap-node.js b/src/bootstrap-node.js index 1484cbb8bed..4f9cf6ecec4 100644 --- a/src/bootstrap-node.js +++ b/src/bootstrap-node.js @@ -10,8 +10,6 @@ const path = require('path'); const fs = require('fs'); const Module = require('module'); - -const isESM = false; // ESM-comment-end // ESM-uncomment-begin // import * as path from 'path'; @@ -20,8 +18,6 @@ const isESM = false; // import { createRequire } from 'node:module'; // // const require = createRequire(import.meta.url); -// const Module = require('module'); -// const isESM = true; // const module = { exports: {} }; // const __dirname = path.dirname(fileURLToPath(import.meta.url)); // ESM-uncomment-end @@ -86,32 +82,31 @@ module.exports.devInjectNodeModuleLookupPath = function (injectPath) { } const Module = require('node:module'); - if (isESM) { - // register a loader hook - // ESM-uncomment-begin - // Module.register('./bootstrap-import.js', { parentURL: import.meta.url, data: injectPath }); - // ESM-uncomment-end - } else { - const nodeModulesPath = path.join(__dirname, '../node_modules'); + // ESM-uncomment-begin + // // register a loader hook + // Module.register('./bootstrap-import.js', { parentURL: import.meta.url, data: injectPath }); + // ESM-uncomment-end + // ESM-comment-begin + const nodeModulesPath = path.join(__dirname, '../node_modules'); - // @ts-ignore - const originalResolveLookupPaths = Module._resolveLookupPaths; + // @ts-ignore + const originalResolveLookupPaths = Module._resolveLookupPaths; - // @ts-ignore - Module._resolveLookupPaths = function (moduleName, parent) { - const paths = originalResolveLookupPaths(moduleName, parent); - if (Array.isArray(paths)) { - for (let i = 0, len = paths.length; i < len; i++) { - if (paths[i] === nodeModulesPath) { - paths.splice(i, 0, injectPath); - break; - } + // @ts-ignore + Module._resolveLookupPaths = function (moduleName, parent) { + const paths = originalResolveLookupPaths(moduleName, parent); + if (Array.isArray(paths)) { + for (let i = 0, len = paths.length; i < len; i++) { + if (paths[i] === nodeModulesPath) { + paths.splice(i, 0, injectPath); + break; } } + } - return paths; - }; - } + return paths; + }; + // ESM-comment-end }; module.exports.removeGlobalNodeModuleLookupPaths = function () { @@ -207,36 +202,34 @@ module.exports.configurePortable = function (product) { * Helper to enable ASAR support. */ module.exports.enableASARSupport = function () { - if (isESM) { - return; // TODO@esm ASAR support is disabled in ESM - } else { - const NODE_MODULES_PATH = path.join(__dirname, '../node_modules'); - const NODE_MODULES_ASAR_PATH = `${NODE_MODULES_PATH}.asar`; + // ESM-comment-begin + const NODE_MODULES_PATH = path.join(__dirname, '../node_modules'); + const NODE_MODULES_ASAR_PATH = `${NODE_MODULES_PATH}.asar`; - // @ts-ignore - const originalResolveLookupPaths = Module._resolveLookupPaths; + // @ts-ignore + const originalResolveLookupPaths = Module._resolveLookupPaths; - // @ts-ignore - Module._resolveLookupPaths = function (request, parent) { - const paths = originalResolveLookupPaths(request, parent); - if (Array.isArray(paths)) { - for (let i = 0, len = paths.length; i < len; i++) { - if (paths[i] === NODE_MODULES_PATH) { - paths.splice(i, 0, NODE_MODULES_ASAR_PATH); - break; - } + // @ts-ignore + Module._resolveLookupPaths = function (request, parent) { + const paths = originalResolveLookupPaths(request, parent); + if (Array.isArray(paths)) { + for (let i = 0, len = paths.length; i < len; i++) { + if (paths[i] === NODE_MODULES_PATH) { + paths.splice(i, 0, NODE_MODULES_ASAR_PATH); + break; } } + } - return paths; - }; - } + return paths; + }; + // ESM-comment-end }; /** * Helper to convert a file path to a URI. * - * TODO@bpasero check for removal once ESM has landed. + * TODO@bpasero TODO@esm check for removal once ESM has landed. * * @param {string} path * @param {{ isWindows?: boolean, scheme?: string, fallbackAuthority?: string }} config diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index b3216ac0f2e..25a4a216ba2 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -15,13 +15,6 @@ /* eslint-disable no-restricted-globals */ -// ESM-comment-begin -const isESM = false; -// ESM-comment-end -// ESM-uncomment-begin -// const isESM = true; -// ESM-uncomment-end - (function (factory) { // @ts-ignore globalThis.MonacoBootstrapWindow = factory(); @@ -95,133 +88,133 @@ const isESM = false; window['MonacoEnvironment'] = {}; - if (isESM) { + // ESM-uncomment-begin + // // Signal before require() + // if (typeof options?.beforeRequire === 'function') { + // options.beforeRequire(configuration); + // } - // Signal before require() - if (typeof options?.beforeRequire === 'function') { - options.beforeRequire(configuration); - } + // const baseUrl = new URL(`${fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out/`); + // globalThis._VSCODE_FILE_ROOT = baseUrl.toString(); - const baseUrl = new URL(`${fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out/`); - globalThis._VSCODE_FILE_ROOT = baseUrl.toString(); + // // DEV --------------------------------------------------------------------------------------- + // // DEV: This is for development and enables loading CSS via import-statements via import-maps. + // // DEV: For each CSS modules that we have we defined an entry in the import map that maps to + // // DEV: a blob URL that loads the CSS via a dynamic @import-rule. + // // DEV --------------------------------------------------------------------------------------- + // if (Array.isArray(configuration.cssModules) && configuration.cssModules.length > 0) { + // performance.mark('code/willAddCssLoader'); - // DEV --------------------------------------------------------------------------------------- - // DEV: This is for development and enables loading CSS via import-statements via import-maps. - // DEV: For each CSS modules that we have we defined an entry in the import map that maps to - // DEV: a blob URL that loads the CSS via a dynamic @import-rule. - // DEV --------------------------------------------------------------------------------------- - if (Array.isArray(configuration.cssModules) && configuration.cssModules.length > 0) { - performance.mark('code/willAddCssLoader'); + // const style = document.createElement('style'); + // style.type = 'text/css'; + // style.media = 'screen'; + // style.id = 'vscode-css-loading'; + // document.head.appendChild(style); - const style = document.createElement('style'); - style.type = 'text/css'; - style.media = 'screen'; - style.id = 'vscode-css-loading'; - document.head.appendChild(style); + // globalThis._VSCODE_CSS_LOAD = function (url) { + // style.textContent += `@import url(${url});\n`; + // }; - globalThis._VSCODE_CSS_LOAD = function (url) { - style.textContent += `@import url(${url});\n`; - }; + // /** + // * @type { { imports: Record }} + // */ + // const importMap = { imports: {} }; + // for (const cssModule of configuration.cssModules) { + // const cssUrl = new URL(cssModule, baseUrl).href; + // const jsSrc = `globalThis._VSCODE_CSS_LOAD('${cssUrl}');\n`; + // const blob = new Blob([jsSrc], { type: 'application/javascript' }); + // importMap.imports[cssUrl] = URL.createObjectURL(blob); + // } - /** - * @type { { imports: Record }} - */ - const importMap = { imports: {} }; - for (const cssModule of configuration.cssModules) { - const cssUrl = new URL(cssModule, baseUrl).href; - const jsSrc = `globalThis._VSCODE_CSS_LOAD('${cssUrl}');\n`; - const blob = new Blob([jsSrc], { type: 'application/javascript' }); - importMap.imports[cssUrl] = URL.createObjectURL(blob); + // const ttp = window.trustedTypes?.createPolicy('vscode-bootstrapImportMap', { createScript(value) { return value; }, }); + // const importMapSrc = JSON.stringify(importMap, undefined, 2); + // const importMapScript = document.createElement('script'); + // importMapScript.type = 'importmap'; + // importMapScript.setAttribute('nonce', '0c6a828f1297'); + // // @ts-ignore + // importMapScript.textContent = ttp?.createScript(importMapSrc) ?? importMapSrc; + // document.head.appendChild(importMapScript); + + // performance.mark('code/didAddCssLoader'); + // } + + // const result = Promise.all(modulePaths.map(modulePath => { + // if (modulePath.includes('vs/css!')) { + // // ESM/CSS when seeing the old `vs/css!` prefix we use that as a signal to + // // load CSS via a tag + // const cssModule = modulePath.replace('vs/css!', ''); + // const link = document.createElement('link'); + // link.rel = 'stylesheet'; + // link.href = new URL(`${cssModule}.css`, baseUrl).href; + // document.head.appendChild(link); + // return Promise.resolve(); + + // } else { + // // ESM/JS module loading + // return import(new URL(`${modulePath}.js`, baseUrl).href); + // } + // })); + + // result.then((res) => invokeResult(res[0]), onUnexpectedError); + // ESM-uncomment-end + + // ESM-comment-begin + /** @type {LoaderConfig} */ + const loaderConfig = { + baseUrl: `${fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out`, + preferScriptTags: true + }; + + // use a trusted types policy when loading via script tags + loaderConfig.trustedTypesPolicy = window.trustedTypes?.createPolicy('amdLoader', { + createScriptURL(value) { + if (value.startsWith(window.location.origin)) { + return value; } - - const ttp = window.trustedTypes?.createPolicy('vscode-bootstrapImportMap', { createScript(value) { return value; }, }); - const importMapSrc = JSON.stringify(importMap, undefined, 2); - const importMapScript = document.createElement('script'); - importMapScript.type = 'importmap'; - importMapScript.setAttribute('nonce', '0c6a828f1297'); - // @ts-ignore - importMapScript.textContent = ttp?.createScript(importMapSrc) ?? importMapSrc; - document.head.appendChild(importMapScript); - - performance.mark('code/didAddCssLoader'); + throw new Error(`Invalid script url: ${value}`); } + }); - const result = Promise.all(modulePaths.map(modulePath => { - if (modulePath.includes('vs/css!')) { - // ESM/CSS when seeing the old `vs/css!` prefix we use that as a signal to - // load CSS via a tag - const cssModule = modulePath.replace('vs/css!', ''); - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = new URL(`${cssModule}.css`, baseUrl).href; - document.head.appendChild(link); - return Promise.resolve(); + // Teach the loader the location of the node modules we use in renderers + // This will enable to load these modules via + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vs/code/browser/workbench/workbench.esm.html b/src/vs/code/browser/workbench/workbench.esm.html new file mode 100644 index 00000000000..38c03e6a321 --- /dev/null +++ b/src/vs/code/browser/workbench/workbench.esm.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index f43e2154419..ca47f74ba49 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -121,6 +121,8 @@ import { Lazy } from 'vs/base/common/lazy'; import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { AuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService'; import { normalizeNFC } from 'vs/base/common/normalization'; +import { ICSSDevelopmentService, CSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; + /** * The main VS Code application. There will only ever be one instance, * even if the user starts many instances (e.g. from the command line). @@ -1101,6 +1103,9 @@ export class CodeApplication extends Disposable { // Proxy Auth services.set(IProxyAuthService, new SyncDescriptor(ProxyAuthService)); + // Dev Only: CSS service (for ESM) + services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true)); + // Init services that require it await Promises.settled([ backupMainService.initialize(), diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.esm.html b/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.esm.html new file mode 100644 index 00000000000..74d159c3ddd --- /dev/null +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.esm.html @@ -0,0 +1,43 @@ + + + + + + + + + +
+ + + + + + + diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer.esm.html b/src/vs/code/electron-sandbox/processExplorer/processExplorer.esm.html new file mode 100644 index 00000000000..d2747202950 --- /dev/null +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.esm.html @@ -0,0 +1,42 @@ + + + + + + + + + + +
+ + + + + diff --git a/src/vs/code/electron-sandbox/workbench/workbench-dev.esm.html b/src/vs/code/electron-sandbox/workbench/workbench-dev.esm.html new file mode 100644 index 00000000000..2d8161be6a9 --- /dev/null +++ b/src/vs/code/electron-sandbox/workbench/workbench-dev.esm.html @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.esm.html b/src/vs/code/electron-sandbox/workbench/workbench.esm.html new file mode 100644 index 00000000000..1e89448e605 --- /dev/null +++ b/src/vs/code/electron-sandbox/workbench/workbench.esm.html @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 9acf37f930f..db947d6c029 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -398,7 +398,10 @@ export async function main(argv: string[]): Promise { return false; } if (target.type === 'page') { - return target.url.indexOf('workbench/workbench.html') > 0 || target.url.indexOf('workbench/workbench-dev.html') > 0; + return target.url.indexOf('workbench/workbench.html') > 0 || + target.url.indexOf('workbench/workbench-dev.html') > 0 || + target.url.indexOf('workbench/workbench.esm.html') > 0 || + target.url.indexOf('workbench/workbench-dev.esm.html') > 0; } else { return true; } diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index aa1e41c8985..6a950ada9e1 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -63,6 +63,7 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ private readonly _logService: ILogService; constructor( + workerMainLocation: URI | undefined, @IModelService modelService: IModelService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @ILogService logService: ILogService, @@ -71,7 +72,7 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ ) { super(); this._modelService = modelService; - this._workerManager = this._register(new WorkerManager(this._modelService, languageConfigurationService)); + this._workerManager = this._register(new WorkerManager(workerMainLocation, this._modelService, languageConfigurationService)); this._logService = logService; // register default link-provider and default completions-provider @@ -281,7 +282,7 @@ class WorkerManager extends Disposable { private _editorWorkerClient: EditorWorkerClient | null; private _lastWorkerUsedTime: number; - constructor(modelService: IModelService, private readonly languageConfigurationService: ILanguageConfigurationService) { + constructor(private readonly workerMainLocation: URI | undefined, modelService: IModelService, private readonly languageConfigurationService: ILanguageConfigurationService) { super(); this._modelService = modelService; this._editorWorkerClient = null; @@ -335,7 +336,7 @@ class WorkerManager extends Disposable { public withWorker(): Promise { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this._modelService, false, 'editorWorkerService', this.languageConfigurationService); + this._editorWorkerClient = new EditorWorkerClient(this.workerMainLocation, this._modelService, false, 'editorWorkerService', this.languageConfigurationService); } return Promise.resolve(this._editorWorkerClient); } @@ -484,6 +485,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien private _disposed = false; constructor( + workerMainLocation: URI | undefined, modelService: IModelService, keepIdleModels: boolean, label: string | undefined, @@ -492,7 +494,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien super(); this._modelService = modelService; this._keepIdleModels = keepIdleModels; - this._workerFactory = new DefaultWorkerFactory(label); + this._workerFactory = new DefaultWorkerFactory(workerMainLocation, label); this._worker = null; this._modelManager = null; } diff --git a/src/vs/editor/browser/services/webWorker.ts b/src/vs/editor/browser/services/webWorker.ts index b5757c58865..8ca8af228ed 100644 --- a/src/vs/editor/browser/services/webWorker.ts +++ b/src/vs/editor/browser/services/webWorker.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { FileAccess } from 'vs/base/common/network'; import { getAllMethodNames } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService'; @@ -14,7 +15,14 @@ import { IModelService } from 'vs/editor/common/services/model'; * Specify an AMD module to load that will `create` an object that will be proxied. */ export function createWebWorker(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker { - return new MonacoWebWorkerImpl(modelService, languageConfigurationService, opts); + return new MonacoWebWorkerImpl(undefined, modelService, languageConfigurationService, opts); +} + +/** + * @internal + */ +export function createWorkbenchWebWorker(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker { + return new MonacoWebWorkerImpl(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, languageConfigurationService, opts); } /** @@ -68,8 +76,8 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement private _foreignModuleCreateData: any | null; private _foreignProxy: Promise | null; - constructor(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions) { - super(modelService, opts.keepIdleModels || false, opts.label, languageConfigurationService); + constructor(workerMainLocation: URI | undefined, modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions) { + super(workerMainLocation, modelService, opts.keepIdleModels || false, opts.label, languageConfigurationService); this._foreignModuleId = opts.moduleId; this._foreignModuleCreateData = opts.createData || null; this._foreignModuleHost = opts.host || null; diff --git a/src/vs/editor/common/services/editorSimpleWorker.esm.ts b/src/vs/editor/common/services/editorSimpleWorker.esm.ts new file mode 100644 index 00000000000..5f659c76f8e --- /dev/null +++ b/src/vs/editor/common/services/editorSimpleWorker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { create } from 'vs/editor/common/services/editorSimpleWorker'; +import { bootstrapSimpleEditorWorker } from './editorWorkerBootstrap'; + +bootstrapSimpleEditorWorker(create); diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 195a870b0af..42c128690c0 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -26,10 +26,18 @@ import { DetailedLineRangeMapping } from '../diff/rangeMapping'; import { linesDiffComputers } from 'vs/editor/common/diff/linesDiffComputers'; import { createProxyObject, getAllMethodNames } from 'vs/base/common/objects'; import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { AppResourcePath, FileAccess } from 'vs/base/common/network'; import { BugIndicatingError } from 'vs/base/common/errors'; import { IDocumentColorComputerTarget, computeDefaultDocumentColors } from 'vs/editor/common/languages/defaultDocumentColorsComputer'; import { FindSectionHeaderOptions, SectionHeader, findSectionHeaders } from 'vs/editor/common/services/findSectionHeaders'; +// ESM-comment-begin +const isESM = false; +// ESM-comment-end +// ESM-uncomment-begin +// const isESM = true; +// ESM-uncomment-end + export interface IMirrorModel extends IMirrorTextModel { readonly uri: URI; readonly version: number; @@ -818,20 +826,21 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // static foreing module return Promise.resolve(getAllMethodNames(this._foreignModule)); } - // ESM-comment-begin + return new Promise((resolve, reject) => { - require([moduleId], (foreignModule: { create: IForeignModuleFactory }) => { + + const onModuleCallback = (foreignModule: { create: IForeignModuleFactory }) => { this._foreignModule = foreignModule.create(ctx, createData); - resolve(getAllMethodNames(this._foreignModule)); + }; - }, reject); + if (!isESM) { + require([`${moduleId}`], onModuleCallback, reject); + } else { + const url = FileAccess.asBrowserUri(`${moduleId}.js` as AppResourcePath).toString(true); + import(`${url}`).then(onModuleCallback).catch(reject); + } }); - // ESM-comment-end - - // ESM-uncomment-begin - // return Promise.reject(new Error(`Unexpected usage`)); - // ESM-uncomment-end } // foreign method request diff --git a/src/vs/editor/common/services/editorWorkerBootstrap.ts b/src/vs/editor/common/services/editorWorkerBootstrap.ts new file mode 100644 index 00000000000..4902f7d48ff --- /dev/null +++ b/src/vs/editor/common/services/editorWorkerBootstrap.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; + +type MessageEvent = { + data: any; +}; + +declare const globalThis: { + postMessage: (message: any) => void; + onmessage: (event: MessageEvent) => void; +}; + +let initialized = false; + +export function initialize(factory: any) { + if (initialized) { + return; + } + initialized = true; + + const simpleWorker = new SimpleWorkerServer((msg) => { + globalThis.postMessage(msg); + }, (host: IEditorWorkerHost) => new EditorSimpleWorker(host, null)); + + globalThis.onmessage = (e: MessageEvent) => { + simpleWorker.onmessage(e.data); + }; +} + +globalThis.onmessage = (e: MessageEvent) => { + // Ignore first message in this case and initialize if not yet initialized + if (!initialized) { + initialize(null); + } +}; + +type CreateFunction = (ctx: C, data: D) => R; + +export function bootstrapSimpleEditorWorker(createFn: CreateFunction) { + globalThis.onmessage = () => { + initialize((ctx: C, createData: D) => { + return createFn.call(self, ctx, createData); + }); + }; +} diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 9afbc5f6075..458c7247477 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -19,8 +19,12 @@ export namespace AccessibilityHelpNLS { export const screenReaderModeEnabled = nls.localize("screenReaderModeEnabled", "Screen Reader Optimized Mode enabled."); export const screenReaderModeDisabled = nls.localize("screenReaderModeDisabled", "Screen Reader Optimized Mode disabled."); export const tabFocusModeOnMsg = nls.localize("tabFocusModeOnMsg", "Pressing Tab in the current editor will move focus to the next focusable element. Toggle this behavior{0}.", ''); - export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior{0}", ''); + export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior{0}.", ''); export const stickScroll = nls.localize("stickScrollKb", "Focus Sticky Scroll{0} to focus the currently nested scopes.", ''); + export const codeFolding = nls.localize("codeFolding", "Use code folding to collapse blocks of code and focus on the code you're interested in via the Toggle Folding Command{0}.", ''); + export const intellisense = nls.localize("intellisense", "Use Intellisense to improve coding efficiency and reduce errors. Trigger suggestions{0}.", ''); + export const showOrFocusHover = nls.localize("showOrFocusHover", "Show or focus the hover{0} to read information about the current symbol.", ''); + export const goToSymbol = nls.localize("goToSymbol", "Go to Symbol{0} to quickly navigate between symbols in the current file.", ''); export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help"); export const listSignalSounds = nls.localize("listSignalSoundsCommand", "Run the command: List Signal Sounds for an overview of all sounds and their current status."); export const listAlerts = nls.localize("listAnnouncementsCommand", "Run the command: List Signal Announcements for an overview of announcements and their current status."); diff --git a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts index 6bc8eb0c1b3..7e0aa0a6eba 100644 --- a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts @@ -253,15 +253,8 @@ export class LightBulbWidget extends Disposable implements IContentWidget { const currLineEmptyOrIndented = isLineEmptyOrIndented(lineNumber); const notEmpty = !nextLineEmptyOrIndented && !prevLineEmptyOrIndented; - let hasDecoration = false; const currLineDecorations = this._editor.getLineDecorations(lineNumber); - if (currLineDecorations) { - for (const decoration of currLineDecorations) { - if (decoration.options.glyphMarginClassName?.includes(Codicon.debugBreakpoint.id)) { - hasDecoration = true; - } - } - } + const hasDecoration = !!(currLineDecorations?.length); // check above and below. if both are blocked, display lightbulb in the gutter. if (!nextLineEmptyOrIndented && !prevLineEmptyOrIndented && !hasDecoration) { diff --git a/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts b/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts index 8bb5e3ad56a..6e3c6f235d2 100644 --- a/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts +++ b/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts @@ -13,6 +13,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import { Disposable } from 'vs/base/common/lifecycle'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; +import { FileAccess } from 'vs/base/common/network'; export class DefaultDocumentColorProvider implements DocumentColorProvider { @@ -22,7 +23,7 @@ export class DefaultDocumentColorProvider implements DocumentColorProvider { modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, ) { - this._editorWorkerClient = new EditorWorkerClient(modelService, false, 'editorWorkerService', languageConfigurationService); + this._editorWorkerClient = new EditorWorkerClient(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, false, 'editorWorkerService', languageConfigurationService); } async provideDocumentColors(model: ITextModel, _token: CancellationToken): Promise { diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController2.ts b/src/vs/editor/contrib/hover/browser/contentHoverController2.ts index f2bc88729ae..4f8a2067093 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController2.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController2.ts @@ -92,7 +92,7 @@ export class ContentHoverController extends Disposable implements IEditorContrib this._hoverSettings = { enabled: hoverOpts.enabled, sticky: hoverOpts.sticky, - hidingDelay: hoverOpts.delay + hidingDelay: hoverOpts.hidingDelay }; if (hoverOpts.enabled) { diff --git a/src/vs/editor/contrib/hover/browser/marginHoverController.ts b/src/vs/editor/contrib/hover/browser/marginHoverController.ts index f478cd93b9e..5b71c6682f1 100644 --- a/src/vs/editor/contrib/hover/browser/marginHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/marginHoverController.ts @@ -77,7 +77,7 @@ export class MarginHoverController extends Disposable implements IEditorContribu this._hoverSettings = { enabled: hoverOpts.enabled, sticky: hoverOpts.sticky, - hidingDelay: hoverOpts.delay + hidingDelay: hoverOpts.hidingDelay }; if (hoverOpts.enabled) { diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index 0e29ea762bc..254524bf36a 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -509,27 +509,42 @@ export class SuggestController implements IEditorContribution { // clear only now - after all tasks are done Promise.all(tasks).finally(() => { - this._reportSuggestionAcceptedTelemetry(item, model, isResolved, _commandExectionDuration, _additionalEditsAppliedAsync, event.index); + this._reportSuggestionAcceptedTelemetry(item, model, isResolved, _commandExectionDuration, _additionalEditsAppliedAsync, event.index, event.model.items); this.model.clear(); cts.dispose(); }); } - private _reportSuggestionAcceptedTelemetry(item: CompletionItem, model: ITextModel, itemResolved: boolean, commandExectionDuration: number, additionalEditsAppliedAsync: number, index: number): void { + private _reportSuggestionAcceptedTelemetry(item: CompletionItem, model: ITextModel, itemResolved: boolean, commandExectionDuration: number, additionalEditsAppliedAsync: number, index: number, completionItems: CompletionItem[]): void { if (Math.floor(Math.random() * 100) === 0) { // throttle telemetry event because accepting completions happens a lot return; } + const labelMap = new Map(); + + for (let i = 0; i < Math.min(30, completionItems.length); i++) { + const label = completionItems[i].textLabel; + + if (labelMap.has(label)) { + labelMap.get(label)!.push(i); + } else { + labelMap.set(label, [i]); + } + } + + const firstIndexArray = labelMap.get(item.textLabel); + const hasDuplicates = firstIndexArray && firstIndexArray.length > 1; + const firstIndex = hasDuplicates ? firstIndexArray[0] : -1; + type AcceptedSuggestion = { extensionId: string; providerId: string; fileExtension: string; languageId: string; basenameHash: string; kind: number; resolveInfo: number; resolveDuration: number; commandDuration: number; additionalEditsAsync: number; - index: number; - label: string; + index: number; firstIndex: number; }; type AcceptedSuggestionClassification = { owner: 'jrieken'; @@ -544,8 +559,8 @@ export class SuggestController implements IEditorContribution { resolveDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How long resolving took to finish' }; commandDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How long a completion item command took' }; additionalEditsAsync: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Info about asynchronously applying additional edits' }; - index: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The order of the completion item in the list.' }; - label: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The label of the completion item.' }; + index: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The index of the completion item in the sorted list.' }; + firstIndex: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'When there are multiple completions, the index of the first instance.' }; }; this._telemetryService.publicLog2('suggest.acceptedSuggestion', { @@ -560,7 +575,7 @@ export class SuggestController implements IEditorContribution { commandDuration: commandExectionDuration, additionalEditsAsync: additionalEditsAppliedAsync, index, - label: item.textLabel + firstIndex, }); } @@ -1010,13 +1025,13 @@ registerEditorCommand(new SuggestCommand({ group: 'right', order: 1, when: ContextKeyExpr.and(SuggestContext.DetailsVisible, SuggestContext.CanResolve), - title: nls.localize('detail.more', "show less") + title: nls.localize('detail.more', "Show Less") }, { menuId: suggestWidgetStatusbarMenu, group: 'right', order: 1, when: ContextKeyExpr.and(SuggestContext.DetailsVisible.toNegated(), SuggestContext.CanResolve), - title: nls.localize('detail.less', "show more") + title: nls.localize('detail.less', "Show More") }] })); diff --git a/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts b/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts index 15f238b1627..65e3b8a4594 100644 --- a/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts @@ -66,7 +66,7 @@ suite('suggest, word distance', function () { private _worker = new EditorSimpleWorker(new class extends mock() { }, null); constructor() { - super(modelService, new class extends mock() { }, new NullLogService(), new TestLanguageConfigurationService(), new LanguageFeaturesService()); + super(undefined, modelService, new class extends mock() { }, new NullLogService(), new TestLanguageConfigurationService(), new LanguageFeaturesService()); this._worker.acceptNewModel({ url: model.uri.toString(), lines: model.getLinesContent(), diff --git a/src/vs/editor/editor.worker.ts b/src/vs/editor/editor.worker.ts index 7c53a6094b2..a50ddd14bad 100644 --- a/src/vs/editor/editor.worker.ts +++ b/src/vs/editor/editor.worker.ts @@ -3,30 +3,4 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker'; -import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; - -let initialized = false; - -export function initialize(foreignModule: any) { - if (initialized) { - return; - } - initialized = true; - - const simpleWorker = new SimpleWorkerServer((msg) => { - globalThis.postMessage(msg); - }, (host: IEditorWorkerHost) => new EditorSimpleWorker(host, foreignModule)); - - globalThis.onmessage = (e: MessageEvent) => { - simpleWorker.onmessage(e.data); - }; -} - -globalThis.onmessage = (e: MessageEvent) => { - // Ignore first message in this case and initialize if not yet initialized - if (!initialized) { - initialize(null); - } -}; +export * from 'vs/editor/common/services/editorWorkerBootstrap'; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 9682a3cfb0f..ca773a2b41d 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/editor/common/languages/languageConfigurationRegistry'; import 'vs/editor/standalone/browser/standaloneCodeEditorService'; import 'vs/editor/standalone/browser/standaloneLayoutService'; import 'vs/platform/undoRedo/common/undoRedoService'; @@ -89,6 +88,8 @@ import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/com import { DefaultConfiguration } from 'vs/platform/configuration/common/configurations'; import { WorkspaceEdit } from 'vs/editor/common/languages'; import { AccessibilitySignal, AccessibilityModality, IAccessibilitySignalService, Sound } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { LogService } from 'vs/platform/log/common/logService'; import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -1072,6 +1073,18 @@ class StandaloneContextMenuService extends ContextMenuService { } } +class StandaloneEditorWorkerService extends EditorWorkerService { + constructor( + @IModelService modelService: IModelService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @ILogService logService: ILogService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + ) { + super(undefined, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); + } +} + class StandaloneAccessbilitySignalService implements IAccessibilitySignalService { _serviceBrand: undefined; async playSignal(cue: AccessibilitySignal, options: {}): Promise { @@ -1130,7 +1143,7 @@ registerSingleton(IContextKeyService, ContextKeyService, InstantiationType.Eager registerSingleton(IProgressService, StandaloneProgressService, InstantiationType.Eager); registerSingleton(IEditorProgressService, StandaloneEditorProgressService, InstantiationType.Eager); registerSingleton(IStorageService, InMemoryStorageService, InstantiationType.Eager); -registerSingleton(IEditorWorkerService, EditorWorkerService, InstantiationType.Eager); +registerSingleton(IEditorWorkerService, StandaloneEditorWorkerService, InstantiationType.Eager); registerSingleton(IBulkEditService, StandaloneBulkEditService, InstantiationType.Eager); registerSingleton(IWorkspaceTrustManagementService, StandaloneWorkspaceTrustManagementService, InstantiationType.Eager); registerSingleton(ITextModelService, StandaloneTextModelService, InstantiationType.Eager); diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 91d9ffdc49d..38f815566e3 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -166,6 +166,7 @@ type MenuItemGroup = [string, Array]; class MenuInfoSnapshot { protected _menuGroups: MenuItemGroup[] = []; + private _allMenuIds: Set = new Set(); private _structureContextKeys: Set = new Set(); private _preconditionContextKeys: Set = new Set(); private _toggledContextKeys: Set = new Set(); @@ -175,6 +176,11 @@ class MenuInfoSnapshot { protected readonly _collectContextKeysForSubmenus: boolean, ) { this.refresh(); + this._allMenuIds.add(_id); + } + + get allMenuIds(): ReadonlySet { + return this._allMenuIds; } get structureContextKeys(): ReadonlySet { @@ -193,6 +199,7 @@ class MenuInfoSnapshot { // reset this._menuGroups.length = 0; + this._allMenuIds.clear(); this._structureContextKeys.clear(); this._preconditionContextKeys.clear(); this._toggledContextKeys.clear(); @@ -209,8 +216,8 @@ class MenuInfoSnapshot { } group[1].push(item); - // keep keys for eventing - this._collectContextKeys(item); + // keep keys and submenu ids for eventing + this._collectContextKeysAndSubmenuIds(item); } } @@ -219,7 +226,7 @@ class MenuInfoSnapshot { return menuItems; } - private _collectContextKeys(item: IMenuItem | ISubmenuItem): void { + private _collectContextKeysAndSubmenuIds(item: IMenuItem | ISubmenuItem): void { MenuInfoSnapshot._fillInKbExprKeys(item.when, this._structureContextKeys); @@ -237,7 +244,9 @@ class MenuInfoSnapshot { } else if (this._collectContextKeysForSubmenus) { // recursively collect context keys from submenus so that this // menu fires events when context key changes affect submenus - MenuRegistry.getMenuItems(item.submenu).forEach(this._collectContextKeys, this); + MenuRegistry.getMenuItems(item.submenu).forEach(this._collectContextKeysAndSubmenuIds, this); + + this._allMenuIds.add(item.submenu); } } @@ -383,8 +392,11 @@ class MenuImpl implements IMenu { }, options.eventDebounceDelay); this._disposables.add(rebuildMenuSoon); this._disposables.add(MenuRegistry.onDidChangeMenu(e => { - if (e.has(id)) { - rebuildMenuSoon.schedule(); + for (const id of this._menuInfo.allMenuIds) { + if (e.has(id)) { + rebuildMenuSoon.schedule(); + break; + } } })); diff --git a/src/vs/platform/cssDev/node/cssDevService.ts b/src/vs/platform/cssDev/node/cssDevService.ts new file mode 100644 index 00000000000..98e73883207 --- /dev/null +++ b/src/vs/platform/cssDev/node/cssDevService.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawn } from 'child_process'; +import { relative } from 'path'; +import { isESM } from 'vs/base/common/amd'; +import { FileAccess } from 'vs/base/common/network'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; + +export const ICSSDevelopmentService = createDecorator('ICSSDevelopmentService'); + +export interface ICSSDevelopmentService { + _serviceBrand: undefined; + isEnabled: boolean; + getCssModules(): Promise; +} + +export class CSSDevelopmentService implements ICSSDevelopmentService { + + declare _serviceBrand: undefined; + + private _cssModules?: Promise; + + constructor( + @IEnvironmentService private readonly envService: IEnvironmentService, + @ILogService private readonly logService: ILogService + ) { } + + get isEnabled(): boolean { + return !this.envService.isBuilt && isESM; + } + + getCssModules(): Promise { + this._cssModules ??= this.computeCssModules(); + return this._cssModules; + } + + private async computeCssModules(): Promise { + if (!this.isEnabled) { + return []; + } + + const rg = await import('@vscode/ripgrep'); + return await new Promise((resolve) => { + + const sw = StopWatch.create(); + + const chunks: string[][] = []; + const decoder = new TextDecoder(); + const basePath = FileAccess.asFileUri('').fsPath; + const process = spawn(rg.rgPath, ['-g', '**/*.css', '--files', '--no-ignore', basePath], {}); + + process.stdout.on('data', data => { + const chunk = decoder.decode(data, { stream: true }); + chunks.push(chunk.split('\n').filter(Boolean)); + }); + process.on('error', err => { + this.logService.error('[CSS_DEV] FAILED to compute CSS data', err); + resolve([]); + }); + process.on('close', () => { + const result = chunks.flat().map(path => relative(basePath, path).replace(/\\/g, '/')).filter(Boolean).sort(); + resolve(result); + this.logService.info(`[CSS_DEV] DONE, ${result.length} css modules (${Math.round(sw.elapsed())}ms)`); + }); + }); + } +} diff --git a/src/vs/platform/environment/test/common/amdX.test.ts b/src/vs/platform/environment/test/common/amdX.test.ts deleted file mode 100644 index 2f454f80d67..00000000000 --- a/src/vs/platform/environment/test/common/amdX.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { importAMDNodeModule } from 'vs/amdX'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { isESM } from 'vs/base/common/amd'; - -(isESM ? suite : suite.skip)('Modules Loading in ESM via importAMDNodeModule()', () => { - - test('@vscode/iconv-lite-umd', async () => { - const module = await importAMDNodeModule('@vscode/iconv-lite-umd', 'lib/iconv-lite-umd.js'); - assert.ok(typeof module.getDecoder === 'function'); - }); - - test.skip('@vscode/tree-sitter-wasm', async () => { - //TODO@esm this cannot be resolved in ESM because the module is not self-contained - const module = await importAMDNodeModule('@vscode/tree-sitter-wasm', 'wasm/tree-sitter.js'); - assert.ok(typeof module.Parser === 'function'); - }); - - test('jschardet', async () => { - const jschardet = await importAMDNodeModule('jschardet', isESM ? 'dist/jschardet.js' : 'dist/jschardet.min.js'); - assert.ok(typeof jschardet.detect === 'function'); - }); - - test('tas-client-umd', async () => { - const tas = await importAMDNodeModule('tas-client-umd', 'lib/tas-client-umd.js'); - assert.ok(typeof tas.ExperimentationService === 'function'); - }); - - test('vscode-oniguruma', async () => { - const oniguruma = await importAMDNodeModule('vscode-oniguruma', 'release/main.js'); - assert.ok(typeof oniguruma.loadWASM === 'function'); - }); - - test('vscode-textmate', async () => { - const textmate = await importAMDNodeModule('vscode-textmate', 'release/main.js'); - assert.ok(typeof textmate.parseRawGrammar === 'function'); - }); - - test('@xterm', async () => { - const xterm = await importAMDNodeModule('@xterm/xterm', 'lib/xterm.js'); - assert.ok(typeof xterm.Terminal === 'function'); - - const addon1 = await importAMDNodeModule('@xterm/addon-clipboard', 'lib/addon-clipboard.js'); - assert.ok(typeof addon1.ClipboardAddon === 'function'); - - const addon2 = await importAMDNodeModule('@xterm/addon-image', 'lib/addon-image.js'); - assert.ok(typeof addon2.ImageAddon === 'function'); - - const addon3 = await importAMDNodeModule('@xterm/addon-search', 'lib/addon-search.js'); - assert.ok(typeof addon3.SearchAddon === 'function'); - - const addon4 = await importAMDNodeModule('@xterm/addon-unicode11', 'lib/addon-unicode11.js'); - assert.ok(typeof addon4.Unicode11Addon === 'function'); - - const addon5 = await importAMDNodeModule('@xterm/addon-webgl', 'lib/addon-webgl.js'); - assert.ok(typeof addon5.WebglAddon === 'function'); - - const addon6 = await importAMDNodeModule('@xterm/addon-serialize', 'lib/addon-serialize.js'); - assert.ok(typeof addon6.SerializeAddon === 'function'); - }); - - test.skip('@vscode/vscode-languagedetection', async () => { // TODO this test prints to console which is disallowed - const languagedetection = await importAMDNodeModule('@vscode/vscode-languagedetection', 'dist/lib/index.js'); - assert.ok(typeof languagedetection.ModelOperations === 'function'); - }); - - ensureNoDisposablesAreLeakedInTestSuite(); -}); diff --git a/src/vs/platform/environment/test/common/esmModule.test.ts b/src/vs/platform/environment/test/common/esmModule.test.ts deleted file mode 100644 index 03cc685eed5..00000000000 --- a/src/vs/platform/environment/test/common/esmModule.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { isWeb } from 'vs/base/common/platform'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { isESM } from 'vs/base/common/amd'; - -(isESM ? suite : suite.skip)('Modules Loading in ESM via direct import()', () => { - - (isWeb ? test.skip : test)('@microsoft/1ds-post-js', async () => { // TODO@esm fails in web? - // eslint-disable-next-line local/code-amd-node-module - const { default: module } = await import('@microsoft/1ds-post-js'); - assert.ok(typeof module.PostChannel === 'function'); - }); - - (isWeb ? test.skip : test)('@microsoft/1ds-core-js', async () => { // TODO@esm fails in web? - // eslint-disable-next-line local/code-amd-node-module - const { default: module } = await import('@microsoft/1ds-core-js'); - assert.ok(typeof module.AppInsightsCore === 'function'); - }); - - ensureNoDisposablesAreLeakedInTestSuite(); -}); diff --git a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts index 953bf14e95e..e9b5ba5c08f 100644 --- a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts +++ b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { isWindows } from 'vs/base/common/platform'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { flakySuite } from 'vs/base/test/common/testUtils'; function testErrorMessage(module: string): string { @@ -13,7 +13,7 @@ function testErrorMessage(module: string): string { flakySuite('Native Modules (all platforms)', () => { - test.skip('kerberos', async () => { // TODO fails on macOS ARM? + (isMacintosh ? test.skip : test)('kerberos', async () => { // Somehow fails on macOS ARM? const { default: kerberos } = await import('kerberos'); assert.ok(typeof kerberos.initializeClient === 'function', testErrorMessage('kerberos')); }); @@ -150,21 +150,6 @@ flakySuite('Native Modules (all platforms)', () => { }); assert.ok(windowsCerts.length > 0, testErrorMessage('@vscode/proxy-agent')); }); - - // These tests require certain modules from `vscode-distro` to work and are otherwise skipped. - // test('vsda', async function () { - // const vsda = await import('vsda'); - // const signer = new vsda.signer(); - // const signed = await signer.sign('value'); - // assert.ok(typeof signed === 'string', testErrorMessage('vsda')); - // assert.ok(typeof (vsda as any).validator === 'function', testErrorMessage('vsda')); - - // }); - - // test('@vscode/vsce-sign', async function () { - // const vsceSign = await import('@vscode/vsce-sign'); - // assert.ok(typeof vsceSign.verify === 'function', testErrorMessage('@vscode/vsce-sign')); - // }); }); (!isWindows ? suite.skip : suite)('Native Modules (Windows)', () => { diff --git a/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts b/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts index 43597893bbf..30d22f4e709 100644 --- a/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts +++ b/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { importAMDNodeModule } from 'vs/amdX'; import { getErrorMessage } from 'vs/base/common/errors'; import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -103,12 +104,12 @@ export class ExtensionSignatureVerificationService implements IExtensionSignatur private async resolveVsceSign(): Promise { // ESM-uncomment-begin + // if (typeof importAMDNodeModule === 'function') { /* fixes unused import, remove me */} // const mod = '@vscode/vsce-sign'; // return import(mod); // ESM-uncomment-end // ESM-comment-begin - const { importAMDNodeModule } = await import('vs/amdX'); return importAMDNodeModule('@vscode/vsce-sign', 'src/main.js'); // ESM-comment-end } diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index ae38c2ebec3..cc1e493b864 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -278,6 +278,9 @@ const _allApiProposals = { notebookMime: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts', }, + notebookReplDocument: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookReplDocument.d.ts', + }, notebookVariableProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts', }, diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 1aea4ead9b9..c17f4331237 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -22,6 +22,8 @@ import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electr import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; import { ICodeWindow, IWindowState } from 'vs/platform/window/electron-main/window'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; +import { isESM } from 'vs/base/common/amd'; +import { ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; interface IBrowserWindowOptions { backgroundColor: string | undefined; @@ -49,6 +51,7 @@ export class IssueMainService implements IIssueMainService { @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @ICSSDevelopmentService private readonly cssDevelopmentService: ICSSDevelopmentService, ) { } //#region Used by renderer @@ -85,11 +88,12 @@ export class IssueMainService implements IIssueMainService { nls: { messages: globalThis._VSCODE_NLS_MESSAGES, language: globalThis._VSCODE_NLS_LANGUAGE - } + }, + cssModules: this.cssDevelopmentService.isEnabled ? await this.cssDevelopmentService.getCssModules() : undefined }); this.issueReporterWindow.loadURL( - FileAccess.asBrowserUri(`vs/workbench/contrib/issue/electron-sandbox/issueReporter${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true) + FileAccess.asBrowserUri(`vs/workbench/contrib/issue/electron-sandbox/issueReporter${this.environmentMainService.isBuilt ? '' : '-dev'}.${isESM ? 'esm.' : ''}html`).toString(true) ); this.issueReporterWindow.on('close', () => { diff --git a/src/vs/platform/issue/electron-main/processMainService.ts b/src/vs/platform/issue/electron-main/processMainService.ts index e6002ae0192..c60f03767a2 100644 --- a/src/vs/platform/issue/electron-main/processMainService.ts +++ b/src/vs/platform/issue/electron-main/processMainService.ts @@ -15,6 +15,7 @@ import { IDiagnosticsService, isRemoteDiagnosticError, PerformanceInfo, SystemIn import { IDiagnosticsMainService } from 'vs/platform/diagnostics/electron-main/diagnosticsMainService'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; import { IProcessMainService, ProcessExplorerData, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; @@ -25,6 +26,7 @@ import { IStateService } from 'vs/platform/state/node/state'; import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; import { IWindowState } from 'vs/platform/window/electron-main/window'; +import { isESM } from 'vs/base/common/amd'; const processExplorerWindowState = 'issue.processExplorerWindowState'; @@ -57,6 +59,7 @@ export class ProcessMainService implements IProcessMainService { @IProtocolMainService private readonly protocolMainService: IProtocolMainService, @IProductService private readonly productService: IProductService, @IStateService private readonly stateService: IStateService, + @ICSSDevelopmentService private readonly cssDevelopmentService: ICSSDevelopmentService ) { this.registerListeners(); } @@ -157,11 +160,12 @@ export class ProcessMainService implements IProcessMainService { nls: { messages: globalThis._VSCODE_NLS_MESSAGES, language: globalThis._VSCODE_NLS_LANGUAGE - } + }, + cssModules: this.cssDevelopmentService.isEnabled ? await this.cssDevelopmentService.getCssModules() : undefined }); this.processExplorerWindow.loadURL( - FileAccess.asBrowserUri(`vs/code/electron-sandbox/processExplorer/processExplorer${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true) + FileAccess.asBrowserUri(`vs/code/electron-sandbox/processExplorer/processExplorer${this.environmentMainService.isBuilt ? '' : '-dev'}.${isESM ? 'esm.' : ''}html`).toString(true) ); this.processExplorerWindow.on('close', () => { diff --git a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.esm.ts b/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.esm.ts new file mode 100644 index 00000000000..4329929cd9c --- /dev/null +++ b/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { create } from './profileAnalysisWorker'; +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; + +bootstrapSimpleWorker(create); diff --git a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts b/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts index ad6a25cd679..f862cc5944a 100644 --- a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts +++ b/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts @@ -5,6 +5,7 @@ import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; +import { FileAccess } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -41,7 +42,7 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { declare _serviceBrand: undefined; - private readonly _workerFactory = new DefaultWorkerFactory('CpuProfileAnalysis'); + private readonly _workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), 'CpuProfileAnalysis'); constructor( @ITelemetryService private readonly _telemetryService: ITelemetryService, diff --git a/src/vs/platform/protocol/electron-main/protocolMainService.ts b/src/vs/platform/protocol/electron-main/protocolMainService.ts index 8b6fd7b5868..4dcb1bcaf7f 100644 --- a/src/vs/platform/protocol/electron-main/protocolMainService.ts +++ b/src/vs/platform/protocol/electron-main/protocolMainService.ts @@ -96,7 +96,8 @@ export class ProtocolMainService extends Disposable implements IProtocolMainServ let headers: Record | undefined; if (this.environmentService.crossOriginIsolated) { - if (basename(path) === 'workbench.html' || basename(path) === 'workbench-dev.html') { + const pathBasename = basename(path); + if (pathBasename === 'workbench.html' || pathBasename === 'workbench-dev.html' || pathBasename === 'workbench.esm.html' || pathBasename === 'workbench-dev.esm.html') { headers = COI.CoopAndCoep; } else { headers = COI.getHeadersFromQuery(request.url); diff --git a/src/vs/platform/sign/node/signService.ts b/src/vs/platform/sign/node/signService.ts index 0b4dcbcf73b..d1c7b1325f7 100644 --- a/src/vs/platform/sign/node/signService.ts +++ b/src/vs/platform/sign/node/signService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { importAMDNodeModule } from 'vs/amdX'; import { AbstractSignService, IVsdaValidator } from 'vs/platform/sign/common/abstractSignService'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -30,13 +31,13 @@ export class SignService extends AbstractSignService implements ISignService { private async vsda(): Promise { // ESM-uncomment-begin + // if (typeof importAMDNodeModule === 'function') { /* fixes unused import, remove me */} // const mod = 'vsda'; // const { default: vsda } = await import(mod); // return vsda; // ESM-uncomment-end // ESM-comment-begin - const { importAMDNodeModule } = await import('vs/amdX'); return importAMDNodeModule('vsda', 'index.js'); // ESM-comment-end } diff --git a/src/vs/platform/telemetry/common/1dsAppender.ts b/src/vs/platform/telemetry/common/1dsAppender.ts index cf06227da41..d8a2619e484 100644 --- a/src/vs/platform/telemetry/common/1dsAppender.ts +++ b/src/vs/platform/telemetry/common/1dsAppender.ts @@ -5,6 +5,7 @@ import type { IExtendedConfiguration, IExtendedTelemetryItem, ITelemetryItem, ITelemetryUnloadState } from '@microsoft/1ds-core-js'; import type { IChannelConfiguration, IXHROverride, PostChannel } from '@microsoft/1ds-post-js'; +import { importAMDNodeModule } from 'vs/amdX'; import { onUnexpectedError } from 'vs/base/common/errors'; import { mixin } from 'vs/base/common/objects'; import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -22,11 +23,11 @@ const endpointHealthUrl = 'https://mobile.events.data.microsoft.com/ping'; async function getClient(instrumentationKey: string, addInternalFlag?: boolean, xhrOverride?: IXHROverride): Promise { // ESM-comment-begin - const { importAMDNodeModule } = await import('vs/amdX'); const oneDs = await importAMDNodeModule('@microsoft/1ds-core-js', 'dist/ms.core.js'); const postPlugin = await importAMDNodeModule('@microsoft/1ds-post-js', 'dist/ms.post.js'); // ESM-comment-end // ESM-uncomment-begin + // if (typeof importAMDNodeModule === 'function') { /* fixes unused import, remove me */} // // eslint-disable-next-line local/code-amd-node-module // const oneDs = await import('@microsoft/1ds-core-js'); // // eslint-disable-next-line local/code-amd-node-module diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 3e905e3ba66..5d3b1c579e8 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -44,6 +44,7 @@ import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electr import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; import { firstOrDefault } from 'vs/base/common/arrays'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { isESM } from 'vs/base/common/amd'; export interface IWindowCreationOptions { readonly state: IWindowState; @@ -1036,7 +1037,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { this.readyState = ReadyState.NAVIGATING; // Load URL - this._win.loadURL(FileAccess.asBrowserUri(`vs/code/electron-sandbox/workbench/workbench${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); + this._win.loadURL(FileAccess.asBrowserUri(`vs/code/electron-sandbox/workbench/workbench${this.environmentMainService.isBuilt ? '' : '-dev'}.${isESM ? 'esm.' : ''}html`).toString(true)); // Remember that we did load const wasLoaded = this.wasLoaded; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index ead271abc31..d6b8a8281e9 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -56,6 +56,7 @@ import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electr import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; +import { ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; //#region Helper Interfaces @@ -229,7 +230,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic @IFileService private readonly fileService: IFileService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService, @IThemeMainService private readonly themeMainService: IThemeMainService, - @IAuxiliaryWindowsMainService private readonly auxiliaryWindowsMainService: IAuxiliaryWindowsMainService + @IAuxiliaryWindowsMainService private readonly auxiliaryWindowsMainService: IAuxiliaryWindowsMainService, + @ICSSDevelopmentService private readonly cssDevelopmentService: ICSSDevelopmentService ) { super(); @@ -1471,7 +1473,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic accessibilitySupport: app.accessibilitySupportEnabled, colorScheme: this.themeMainService.getColorScheme(), policiesData: this.policyService.serialize(), - continueOn: this.environmentMainService.continueOn + continueOn: this.environmentMainService.continueOn, + + cssModules: this.cssDevelopmentService.isEnabled ? await this.cssDevelopmentService.getCssModules() : undefined }; // New window diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index d9bb0122f64..c076b5c1150 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -9,7 +9,7 @@ import * as http from 'http'; import * as net from 'net'; import { performance } from 'perf_hooks'; import * as url from 'url'; -import { LoaderStats } from 'vs/base/common/amd'; +import { LoaderStats, isESM } from 'vs/base/common/amd'; import { VSBuffer } from 'vs/base/common/buffer'; import { CharCode } from 'vs/base/common/charCode'; import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; @@ -781,7 +781,7 @@ export async function createServer(address: string | net.AddressInfo | null, arg serverBasePath = `/${serverBasePath}`; } - const hasWebClient = fs.existsSync(FileAccess.asFileUri('vs/code/browser/workbench/workbench.html').fsPath); + const hasWebClient = fs.existsSync(FileAccess.asFileUri(`vs/code/browser/workbench/workbench.${isESM ? 'esm.' : ''}html`).fsPath); if (hasWebClient && address && typeof address !== 'string') { // ships the web ui! diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 46f2f004655..f9ca0e7c399 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -77,6 +77,7 @@ import { RemoteExtensionsScannerChannel, RemoteExtensionsScannerService } from ' import { RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { RemoteUserDataProfilesServiceChannel } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; import { NodePtyHostStarter } from 'vs/platform/terminal/node/nodePtyHostStarter'; +import { CSSDevelopmentService, ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; const eventPrefix = 'monacoworkbench'; @@ -131,6 +132,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IUserDataProfilesService, userDataProfilesService); socketServer.registerChannel('userDataProfiles', new RemoteUserDataProfilesServiceChannel(userDataProfilesService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); + // Dev Only: CSS service (for ESM) + services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true)); + // Initialize const [, , machineId, sqmId, devDeviceId] = await Promise.all([ configurationService.initialize(), diff --git a/src/vs/server/node/webClientServer.ts b/src/vs/server/node/webClientServer.ts index 2abd1e1366c..f66972fe319 100644 --- a/src/vs/server/node/webClientServer.ts +++ b/src/vs/server/node/webClientServer.ts @@ -28,6 +28,8 @@ import { IProductConfiguration } from 'vs/base/common/product'; import { isString } from 'vs/base/common/types'; import { CharCode } from 'vs/base/common/charCode'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { isESM } from 'vs/base/common/amd'; +import { ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; const textMimeType: { [ext: string]: string | undefined } = { '.html': 'text/html', @@ -108,6 +110,7 @@ export class WebClientServer { @ILogService private readonly _logService: ILogService, @IRequestService private readonly _requestService: IRequestService, @IProductService private readonly _productService: IProductService, + @ICSSDevelopmentService private readonly _cssDevService: ICSSDevelopmentService ) { this._webExtensionResourceUrlTemplate = this._productService.extensionsGallery?.resourceUrlTemplate ? URI.parse(this._productService.extensionsGallery.resourceUrlTemplate) : undefined; @@ -297,7 +300,7 @@ export class WebClientServer { const resolveWorkspaceURI = (defaultLocation?: string) => defaultLocation && URI.file(path.resolve(defaultLocation)).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }); - const filePath = FileAccess.asFileUri(this._environmentService.isBuilt ? 'vs/code/browser/workbench/workbench.html' : 'vs/code/browser/workbench/workbench-dev.html').fsPath; + const filePath = FileAccess.asFileUri(`vs/code/browser/workbench/workbench${this._environmentService.isBuilt ? '' : '-dev'}.${isESM ? 'esm.' : ''}html`).fsPath; const authSessionInfo = !this._environmentService.isBuilt && this._environmentService.args['github-auth'] ? { id: generateUuid(), providerId: 'github', @@ -356,6 +359,16 @@ export class WebClientServer { WORKBENCH_NLS_FALLBACK_URL: `${this._staticRoute}/out/nls.messages.js` }; + // DEV --------------------------------------------------------------------------------------- + // DEV: This is for development and enables loading CSS via import-statements via import-maps. + // DEV: The server needs to send along all CSS modules so that the client can construct the + // DEV: import-map. + // DEV --------------------------------------------------------------------------------------- + if (this._cssDevService.isEnabled) { + const cssModules = await this._cssDevService.getCssModules(); + values['WORKBENCH_DEV_CSS_MODULES'] = JSON.stringify(cssModules); + } + if (useTestResolver) { const bundledExtensions: { extensionPath: string; packageJSON: IExtensionManifest }[] = []; for (const extensionPath of ['vscode-test-resolver', 'github-authentication']) { @@ -374,13 +387,15 @@ export class WebClientServer { return void res.end('Not found'); } - const webWorkerExtensionHostIframeScriptSHA = 'sha256-V28GQnL3aYxbwgpV3yW1oJ+VKKe/PBSzWntNyH8zVXA='; + const webWorkerExtensionHostIframeScriptSHA = isESM ? 'sha256-2Q+j4hfT09+1+imS46J2YlkCtHWQt0/BE79PXjJ0ZJ8=' : 'sha256-V28GQnL3aYxbwgpV3yW1oJ+VKKe/PBSzWntNyH8zVXA='; const cspDirectives = [ 'default-src \'self\';', 'img-src \'self\' https: data: blob:;', 'media-src \'self\';', - `script-src 'self' 'unsafe-eval' ${WORKBENCH_NLS_BASE_URL ?? ''} ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html + isESM ? + `script-src 'self' 'unsafe-eval' ${WORKBENCH_NLS_BASE_URL ?? ''} blob: 'nonce-1nline-m4p' ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' 'sha256-/r7rqQ+yrxt57sxLuQ6AMYcy/lUpvAIzHjIJt/OeLWU=' ${useTestResolver ? '' : `http://${remoteAuthority}`};` : // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html + `script-src 'self' 'unsafe-eval' ${WORKBENCH_NLS_BASE_URL ?? ''} ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html 'child-src \'self\';', `frame-src 'self' https://*.vscode-cdn.net data:;`, 'worker-src \'self\' data: blob:;', diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts b/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts index c18d0ac258a..73a5b5a7dba 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts @@ -125,30 +125,45 @@ export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsS } } - async $tryCreateNotebook(options: { viewType: string; content?: NotebookDataDto }): Promise { - const ref = await this._notebookEditorModelResolverService.resolve({ untitledResource: undefined }, options.viewType); - - // untitled notebooks are disposed when they get saved. we should not hold a reference - // to such a disposed notebook and therefore dispose the reference as well - ref.object.notebook.onWillDispose(() => { - ref.dispose(); - }); - - // untitled notebooks are dirty by default - this._proxy.$acceptDirtyStateChanged(ref.object.resource, true); - - // apply content changes... slightly HACKY -> this triggers a change event if (options.content) { - const data = NotebookDto.fromNotebookDataDto(options.content); - ref.object.notebook.reset(data.cells, data.metadata, ref.object.notebook.transientOptions); + const ref = await this._notebookEditorModelResolverService.resolve({ untitledResource: undefined }, options.viewType); + + // untitled notebooks are disposed when they get saved. we should not hold a reference + // to such a disposed notebook and therefore dispose the reference as well + ref.object.notebook.onWillDispose(() => { + ref.dispose(); + }); + + // untitled notebooks with content are dirty by default + this._proxy.$acceptDirtyStateChanged(ref.object.resource, true); + + // apply content changes... slightly HACKY -> this triggers a change event + if (options.content) { + const data = NotebookDto.fromNotebookDataDto(options.content); + ref.object.notebook.reset(data.cells, data.metadata, ref.object.notebook.transientOptions); + } + return ref.object.notebook.uri; + } else { + // If we aren't adding content, we don't need to resolve the full editor model yet. + // This will allow us to adjust settings when the editor is opened, e.g. scratchpad + const notebook = await this._notebookEditorModelResolverService.createUntitledNotebookTextModel(options.viewType); + return notebook.uri; } - return ref.object.resource; } async $tryOpenNotebook(uriComponents: UriComponents): Promise { const uri = URI.revive(uriComponents); const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined); + + if (uriComponents.scheme === 'untitled') { + // untitled notebooks are disposed when they get saved. we should not hold a reference + // to such a disposed notebook and therefore dispose the reference as well + ref.object.notebook.onWillDispose(() => { + ref.dispose(); + }); + } + this._modelReferenceCollection.add(uri, ref); return uri; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 6eec236253a..fe252fca2d2 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -107,7 +107,7 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { ExcludeSettingOptions, oldToNewTextSearchResult, TextSearchCompleteMessageType, TextSearchCompleteMessageTypeNew, TextSearchContextNew, TextSearchMatchNew } from 'vs/workbench/services/search/common/searchExtTypes'; +import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchCompleteMessageTypeNew, TextSearchContextNew, TextSearchMatchNew, oldToNewTextSearchResult } from 'vs/workbench/services/search/common/searchExtTypes'; import type * as vscode from 'vscode'; export interface IExtensionRegistries { @@ -1818,6 +1818,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, ChatResponseDetectedParticipantPart: extHostTypes.ChatResponseDetectedParticipantPart, ChatResponseConfirmationPart: extHostTypes.ChatResponseConfirmationPart, + ChatResponseMovePart: extHostTypes.ChatResponseMovePart, ChatResponseReferencePartStatusKind: extHostTypes.ChatResponseReferencePartStatusKind, ChatRequestTurn: extHostTypes.ChatRequestTurn, ChatResponseTurn: extHostTypes.ChatResponseTurn, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index c734bda14dd..4a1a50ae1ab 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -23,7 +23,7 @@ import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extH import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult, IChatAgentResultTimings } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatAgentVoteDirection, IChatContentReference, IChatFollowup, IChatResponseErrorDetails, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -48,7 +48,7 @@ class ChatAgentResponseStream { this._isClosed = true; } - get timings() { + get timings(): IChatAgentResultTimings { return { firstProgress: this._firstProgress, totalElapsed: this._stopWatch.elapsed() @@ -72,7 +72,7 @@ class ChatAgentResponseStream { const _report = (progress: IChatProgressDto, task?: (progress: vscode.Progress) => Thenable) => { // Measure the time to the first progress update with real markdown content - if (typeof this._firstProgress === 'undefined' && 'content' in progress) { + if (typeof this._firstProgress === 'undefined' && (progress.kind === 'markdownContent' || progress.kind === 'markdownVuln')) { this._firstProgress = this._stopWatch.elapsed(); } @@ -241,7 +241,8 @@ class ChatAgentResponseStream { part instanceof extHostTypes.ChatResponseDetectedParticipantPart || part instanceof extHostTypes.ChatResponseWarningPart || part instanceof extHostTypes.ChatResponseConfirmationPart || - part instanceof extHostTypes.ChatResponseCodeCitationPart + part instanceof extHostTypes.ChatResponseCodeCitationPart || + part instanceof extHostTypes.ChatResponseMovePart ) { checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 5b707fc865e..3f7c3bcd2f8 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -145,8 +145,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { return result; } - - private static _convertNotebookRegistrationData(extension: IExtensionDescription, registration: vscode.NotebookRegistrationData | undefined): INotebookContributionData | undefined { if (!registration) { return; @@ -205,13 +203,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { return assertIsDefined(document?.apiNotebook); } - - async showNotebookDocument(notebookOrUri: vscode.NotebookDocument | URI, options?: vscode.NotebookDocumentShowOptions): Promise { - - if (URI.isUri(notebookOrUri)) { - notebookOrUri = await this.openNotebookDocument(notebookOrUri); - } - + async showNotebookDocument(notebook: vscode.NotebookDocument, options?: vscode.NotebookDocumentShowOptions): Promise { let resolvedOptions: INotebookDocumentShowOptions; if (typeof options === 'object') { resolvedOptions = { @@ -222,11 +214,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { }; } else { resolvedOptions = { - preserveFocus: false + preserveFocus: false, + pinned: true }; } - const editorId = await this._notebookEditorsProxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.notebookType, resolvedOptions); + const viewType = options?.asRepl ? 'repl' : notebook.notebookType; + const editorId = await this._notebookEditorsProxy.$tryShowNotebookDocument(notebook.uri, viewType, resolvedOptions); const editor = editorId && this._editors.get(editorId)?.apiEditor; if (editor) { @@ -234,9 +228,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } if (editorId) { - throw new Error(`Could NOT open editor for "${notebookOrUri.uri.toString()}" because another editor opened in the meantime.`); + throw new Error(`Could NOT open editor for "${notebook.uri.toString()}" because another editor opened in the meantime.`); } else { - throw new Error(`Could NOT open editor for "${notebookOrUri.uri.toString()}".`); + throw new Error(`Could NOT open editor for "${notebook.uri.toString()}".`); } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 140fd24afbb..32e37f0b1ef 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -40,7 +40,7 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatRequestVariableEntry } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatAgentDetection, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatAgentDetection, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatProgressMessage, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from 'vs/workbench/contrib/chat/common/chatService'; import { IToolData, IToolResult } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import * as chatProvider from 'vs/workbench/contrib/chat/common/languageModels'; import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; @@ -2454,6 +2454,19 @@ export namespace ChatResponseWarningPart { } } +export namespace ChatResponseMovePart { + export function from(part: vscode.ChatResponseMovePart): Dto { + return { + kind: 'move', + uri: part.uri, + range: Range.from(part.range), + }; + } + export function to(part: Dto): vscode.ChatResponseMovePart { + return new types.ChatResponseMovePart(URI.revive(part.uri), Range.to(part.range)); + } +} + export namespace ChatTask { export function from(part: vscode.ChatResponseProgressPart2): IChatTaskDto { return { @@ -2561,7 +2574,7 @@ export namespace ChatResponseCodeCitationPart { export namespace ChatResponsePart { - export function from(part: vscode.ChatResponsePart | vscode.ChatResponseTextEditPart | vscode.ChatResponseMarkdownWithVulnerabilitiesPart | vscode.ChatResponseDetectedParticipantPart | vscode.ChatResponseWarningPart | vscode.ChatResponseConfirmationPart | vscode.ChatResponseReferencePart2, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto { + export function from(part: vscode.ChatResponsePart | vscode.ChatResponseTextEditPart | vscode.ChatResponseMarkdownWithVulnerabilitiesPart | vscode.ChatResponseDetectedParticipantPart | vscode.ChatResponseWarningPart | vscode.ChatResponseConfirmationPart | vscode.ChatResponseReferencePart2 | vscode.ChatResponseMovePart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto { if (part instanceof types.ChatResponseMarkdownPart) { return ChatResponseMarkdownPart.from(part); } else if (part instanceof types.ChatResponseAnchorPart) { @@ -2586,6 +2599,8 @@ export namespace ChatResponsePart { return ChatResponseConfirmationPart.from(part); } else if (part instanceof types.ChatResponseCodeCitationPart) { return ChatResponseCodeCitationPart.from(part); + } else if (part instanceof types.ChatResponseMovePart) { + return ChatResponseMovePart.from(part); } return { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index b2693963f6f..6e232711ebb 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4486,6 +4486,14 @@ export class ChatResponseCodeCitationPart { } } +export class ChatResponseMovePart { + constructor( + public readonly uri: vscode.Uri, + public readonly range: vscode.Range, + ) { + } +} + export class ChatResponseTextEditPart { uri: vscode.Uri; edits: vscode.TextEdit[]; diff --git a/src/vs/workbench/api/worker/extensionHostWorker.esm.ts b/src/vs/workbench/api/worker/extensionHostWorker.esm.ts new file mode 100644 index 00000000000..10a4278a7e5 --- /dev/null +++ b/src/vs/workbench/api/worker/extensionHostWorker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { create } from './extensionHostWorker'; + +const data = create(); +self.onmessage = (e) => data.onmessage(e.data); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index d83ac066c6e..bf162986ab6 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -295,7 +295,7 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.editor.enablePreview': { 'type': 'boolean', - 'description': localize('enablePreview', "Controls whether opened editors show as preview editors. Preview editors do not stay open, are reused until explicitly set to be kept open (via double-click or editing), and show file names in italics."), + 'description': localize('enablePreview', "Controls whether preview mode is used when editors open. There is a maximum of one preview mode editor per editor group. This editor displays its filename in italics on its tab or title label and in the Open Editors view. Its contents will be replaced by the next editor opened in preview mode. Making a change in a preview mode editor will persist it, as will a double-click on its label, or the 'Keep Open' option in its label context menu. Opening a file from Explorer with a double-click persists its editor immediately."), 'default': true }, 'workbench.editor.enablePreviewFromQuickOpen': { diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index ce6c600768b..003ee0d115a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -94,6 +94,10 @@ class EditorAccessibilityHelpProvider extends Disposable implements IAccessibleV } else { content.push(AccessibilityHelpNLS.tabFocusModeOffMsg); } + content.push(AccessibilityHelpNLS.codeFolding); + content.push(AccessibilityHelpNLS.intellisense); + content.push(AccessibilityHelpNLS.showOrFocusHover); + content.push(AccessibilityHelpNLS.goToSymbol); content.push(AccessibilityHelpNLS.startDebugging); content.push(AccessibilityHelpNLS.setBreakpoint); content.push(AccessibilityHelpNLS.debugExecuteSelection); diff --git a/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts b/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts index 733c0e50037..1c205fd57ec 100644 --- a/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts +++ b/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts @@ -8,7 +8,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize, localize2 } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; @@ -26,23 +26,51 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, options?: { providerId: string; accountLabel: string }): Promise { - const productService = accessor.get(IProductService); - const extensionService = accessor.get(IExtensionService); - const dialogService = accessor.get(IDialogService); - const quickInputService = accessor.get(IQuickInputService); - const authenticationService = accessor.get(IAuthenticationService); - const authenticationUsageService = accessor.get(IAuthenticationUsageService); - const authenticationAccessService = accessor.get(IAuthenticationAccessService); + override run(accessor: ServicesAccessor, options?: { providerId: string; accountLabel: string }): Promise { + const instantiationService = accessor.get(IInstantiationService); + return instantiationService.createInstance(ManageTrustedExtensionsForAccountActionImpl).run(options); + } +} - let providerId = options?.providerId; - let accountLabel = options?.accountLabel; +interface TrustedExtensionsQuickPickItem extends IQuickPickItem { + extension: AllowedExtension; + lastUsed?: number; +} +class ManageTrustedExtensionsForAccountActionImpl { + constructor( + @IProductService private readonly _productService: IProductService, + @IExtensionService private readonly _extensionService: IExtensionService, + @IDialogService private readonly _dialogService: IDialogService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + @IAuthenticationService private readonly _authenticationService: IAuthenticationService, + @IAuthenticationUsageService private readonly _authenticationUsageService: IAuthenticationUsageService, + @IAuthenticationAccessService private readonly _authenticationAccessService: IAuthenticationAccessService + ) { } + + async run(options?: { providerId: string; accountLabel: string }) { + const { providerId, accountLabel } = await this._resolveProviderAndAccountLabel(options?.providerId, options?.accountLabel); + if (!providerId || !accountLabel) { + return; + } + + const items = await this._getItems(providerId, accountLabel); + if (!items.length) { + return; + } + const disposables = new DisposableStore(); + const picker = this._createQuickPick(disposables, providerId, accountLabel); + picker.items = items; + picker.selectedItems = items.filter((i): i is TrustedExtensionsQuickPickItem => i.type !== 'separator' && !!i.picked); + picker.show(); + } + + private async _resolveProviderAndAccountLabel(providerId: string | undefined, accountLabel: string | undefined) { if (!providerId || !accountLabel) { const accounts = new Array<{ providerId: string; providerLabel: string; accountLabel: string }>(); - for (const id of authenticationService.getProviderIds()) { - const providerLabel = authenticationService.getProvider(id).label; - const sessions = await authenticationService.getSessions(id); + for (const id of this._authenticationService.getProviderIds()) { + const providerLabel = this._authenticationService.getProvider(id).label; + const sessions = await this._authenticationService.getSessions(id); const uniqueAccountLabels = new Set(); for (const session of sessions) { if (!uniqueAccountLabels.has(session.account.label)) { @@ -52,7 +80,7 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { } } - const pick = await quickInputService.pick( + const pick = await this._quickInputService.pick( accounts.map(account => ({ providerId: account.providerId, label: account.accountLabel, @@ -68,12 +96,15 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { providerId = pick.providerId; accountLabel = pick.label; } else { - return; + return { providerId: undefined, accountLabel: undefined }; } } + return { providerId, accountLabel }; + } - const allowedExtensions = authenticationAccessService.readAllowedExtensions(providerId, accountLabel); - const trustedExtensionAuthAccess = productService.trustedExtensionAuthAccess; + private async _getItems(providerId: string, accountLabel: string) { + const allowedExtensions = this._authenticationAccessService.readAllowedExtensions(providerId, accountLabel); + const trustedExtensionAuthAccess = this._productService.trustedExtensionAuthAccess; const trustedExtensionIds = // Case 1: trustedExtensionAuthAccess is an array Array.isArray(trustedExtensionAuthAccess) @@ -86,7 +117,7 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { const allowedExtension = allowedExtensions.find(ext => ext.id === extensionId); if (!allowedExtension) { // Add the extension to the allowedExtensions list - const extension = await extensionService.getExtension(extensionId); + const extension = await this._extensionService.getExtension(extensionId); if (extension) { allowedExtensions.push({ id: extensionId, @@ -103,21 +134,11 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { } if (!allowedExtensions.length) { - dialogService.info(localize('noTrustedExtensions', "This account has not been used by any extensions.")); - return; + this._dialogService.info(localize('noTrustedExtensions', "This account has not been used by any extensions.")); + return []; } - interface TrustedExtensionsQuickPickItem extends IQuickPickItem { - extension: AllowedExtension; - lastUsed?: number; - } - - const disposableStore = new DisposableStore(); - const quickPick = disposableStore.add(quickInputService.createQuickPick({ useSeparators: true })); - quickPick.canSelectMany = true; - quickPick.customButton = true; - quickPick.customLabel = localize('manageTrustedExtensions.cancel', 'Cancel'); - const usages = authenticationUsageService.readAccountUsages(providerId, accountLabel); + const usages = this._authenticationUsageService.readAccountUsages(providerId, accountLabel); const trustedExtensions = []; const otherExtensions = []; for (const extension of allowedExtensions) { @@ -131,34 +152,43 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { } const sortByLastUsed = (a: AllowedExtension, b: AllowedExtension) => (b.lastUsed || 0) - (a.lastUsed || 0); - const toQuickPickItem = function (extension: AllowedExtension): IQuickPickItem & { extension: AllowedExtension } { - const lastUsed = extension.lastUsed; - const description = lastUsed - ? localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true)) - : localize('notUsed', "Has not used this account"); - let tooltip: string | undefined; - let disabled: boolean | undefined; - if (extension.trusted) { - tooltip = localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account"); - disabled = true; - } - return { - label: extension.name, - extension, - description, - tooltip, - disabled, - picked: extension.allowed === undefined || extension.allowed - }; - }; - const items: Array = [ - ...otherExtensions.sort(sortByLastUsed).map(toQuickPickItem), - { type: 'separator', label: localize('trustedExtensions', "Trusted by Microsoft") }, - ...trustedExtensions.sort(sortByLastUsed).map(toQuickPickItem) + + const items = [ + ...otherExtensions.sort(sortByLastUsed).map(this._toQuickPickItem), + { type: 'separator', label: localize('trustedExtensions', "Trusted by Microsoft") } satisfies IQuickPickSeparator, + ...trustedExtensions.sort(sortByLastUsed).map(this._toQuickPickItem) ]; - quickPick.items = items; - quickPick.selectedItems = items.filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator' && (item.extension.allowed === undefined || item.extension.allowed)); + return items; + } + + private _toQuickPickItem(extension: AllowedExtension): TrustedExtensionsQuickPickItem { + const lastUsed = extension.lastUsed; + const description = lastUsed + ? localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true)) + : localize('notUsed', "Has not used this account"); + let tooltip: string | undefined; + let disabled: boolean | undefined; + if (extension.trusted) { + tooltip = localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account"); + disabled = true; + } + return { + label: extension.name, + extension, + description, + tooltip, + disabled, + picked: extension.allowed === undefined || extension.allowed + }; + } + + private _createQuickPick(disposableStore: DisposableStore, providerId: string, accountLabel: string) { + const quickPick = disposableStore.add(this._quickInputService.createQuickPick({ useSeparators: true })); + quickPick.canSelectMany = true; + quickPick.customButton = true; + quickPick.customLabel = localize('manageTrustedExtensions.cancel', 'Cancel'); + quickPick.title = localize('manageTrustedExtensions', "Manage Trusted Extensions"); quickPick.placeholder = localize('manageExtensions', "Choose which extensions can access this account"); @@ -171,7 +201,7 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { updatedAllowedList.forEach(extension => { extension.allowed = allowedExtensionsSet.has(extension); }); - authenticationAccessService.updateAllowedExtensions(providerId, accountLabel, updatedAllowedList); + this._authenticationAccessService.updateAllowedExtensions(providerId, accountLabel, updatedAllowedList); quickPick.hide(); })); @@ -182,8 +212,6 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { disposableStore.add(quickPick.onDidCustom(() => { quickPick.hide(); })); - - quickPick.show(); + return quickPick; } - } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index c8bc363bd34..6030d0c09ec 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -37,7 +37,7 @@ export function registerChatTitleActions() { id: MenuId.ChatMessageTitle, group: 'navigation', order: 1, - when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_VOTE_UP_ENABLED) + when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_VOTE_UP_ENABLED, CONTEXT_RESPONSE_FILTERED.negate()) } }); } @@ -77,7 +77,7 @@ export function registerChatTitleActions() { id: MenuId.ChatMessageTitle, group: 'navigation', order: 2, - when: CONTEXT_RESPONSE + when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED.negate()) } }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 2dbc20d8e56..d9b92a5a532 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -353,7 +353,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const context: IChatContentPartRenderContext = { - element, - index, - content: value, - preceedingContentParts: parts, - }; - const newPart = this.renderChatContentPart(data, templateData, context); - if (newPart) { - templateData.value.appendChild(newPart.domNode); - parts.push(newPart); - } - }); + if (!isFiltered) { + value.forEach((data, index) => { + const context: IChatContentPartRenderContext = { + element, + index, + content: value, + preceedingContentParts: parts, + }; + const newPart = this.renderChatContentPart(data, templateData, context); + if (newPart) { + templateData.value.appendChild(newPart.domNode); + parts.push(newPart); + } + }); + } + if (templateData.renderedParts) { dispose(templateData.renderedParts); } templateData.renderedParts = parts; - if (isRequestVM(element) && element.variables.length) { - const newPart = this.renderAttachments(element.variables, element.contentReferences, templateData); - if (newPart) { - templateData.value.appendChild(newPart.domNode); - templateData.elementDisposables.add(newPart); + if (!isFiltered) { + if (isRequestVM(element) && element.variables.length) { + const newPart = this.renderAttachments(element.variables, element.contentReferences, templateData); + if (newPart) { + templateData.value.appendChild(newPart.domNode); + templateData.elementDisposables.add(newPart); + } } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index de193200bc1..1c364da9c1d 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -648,12 +648,6 @@ color: var(--vscode-icon-foreground) !important; } -.interactive-item-container.filtered-response .value > .rendered-markdown { - pointer-events: none; - -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.05) 60%, rgba(0, 0, 0, 0.00) 80%); - mask-image: linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.05) 60%, rgba(0, 0, 0, 0.00) 80%); -} - /* #region Quick Chat */ .quick-input-widget .interactive-session .interactive-input-part { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 64696de3395..51924c2c0c7 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -152,12 +152,14 @@ export interface IChatQuestion { readonly command?: string; } +export interface IChatAgentResultTimings { + firstProgress?: number; + totalElapsed: number; +} + export interface IChatAgentResult { errorDetails?: IChatResponseErrorDetails; - timings?: { - firstProgress?: number; - totalElapsed: number; - }; + timings?: IChatAgentResultTimings; /** Extra properties that the agent can use to identify a result */ readonly metadata?: { readonly [key: string]: any }; nextQuestion?: IChatQuestion; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index d3aa7f35dd8..370672d599c 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -15,6 +15,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { URI, UriComponents, UriDto, isUriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IRange } from 'vs/editor/common/core/range'; import { TextEdit } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -592,7 +593,9 @@ export type IChatChangeEvent = | IChatInitEvent | IChatAddRequestEvent | IChatChangedRequestEvent | IChatRemoveRequestEvent | IChatAddResponseEvent - | IChatSetAgentEvent; + | IChatSetAgentEvent + | IChatMoveEvent + ; export interface IChatAddRequestEvent { kind: 'addRequest'; @@ -633,6 +636,12 @@ export interface IChatRemoveRequestEvent { reason: ChatRequestRemovalReason; } +export interface IChatMoveEvent { + kind: 'move'; + target: URI; + range: IRange; +} + export interface IChatSetAgentEvent { kind: 'setAgent'; agent: IChatAgentData; @@ -949,6 +958,8 @@ export class ChatModel extends Disposable implements IChatModel { } } else if (progress.kind === 'codeCitation') { request.response.applyCodeCitation(progress); + } else if (progress.kind === 'move') { + this._onDidChange.fire({ kind: 'move', target: progress.uri, range: progress.range }); } else { this.logService.error(`Couldn't handle progress: ${JSON.stringify(progress)}`); } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 292c9a29cf7..4bc74022ae4 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -164,6 +164,12 @@ export interface IChatCommandButton { kind: 'command'; } +export interface IChatMoveMessage { + uri: URI; + range: IRange; + kind: 'move'; +} + export interface IChatTextEdit { uri: URI; edits: TextEdit[]; @@ -194,6 +200,7 @@ export type IChatProgress = | IChatCommandButton | IChatWarningMessage | IChatTextEdit + | IChatMoveMessage | IChatConfirmation; export interface IChatFollowup { diff --git a/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts b/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts new file mode 100644 index 00000000000..5eb837a9509 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { FileAccess } from 'vs/base/common/network'; +import { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { ILogService } from 'vs/platform/log/common/log'; + +export class WorkbenchEditorWorkerService extends EditorWorkerService { + constructor( + @IModelService modelService: IModelService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @ILogService logService: ILogService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + ) { + super(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); + } +} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css index 9179fa761d3..6a7ab84a23a 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css @@ -67,7 +67,7 @@ bottom: 9px; left: 1px; color: inherit; - font-size: 80%; + font-size: 80% !important; } .extension-bookmark .recommendation { diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 1311bd1839b..c17a5b80692 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -204,8 +204,8 @@ CommandsRegistry.registerCommand({ if (resources.length === 2) { return editorService.openEditor({ - original: { resource: resources[1] }, - modified: { resource: resources[0] }, + original: { resource: resources[0] }, + modified: { resource: resources[1] }, options: { pinned: true } }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts index 2cd1fc77dd4..c7d23e85dd1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts @@ -38,7 +38,7 @@ export class InlineChatContentWidget implements IContentWidget { private readonly _inputContainer = document.createElement('div'); private readonly _toolbarContainer = document.createElement('div'); - private _position?: IPosition; + private _position?: IContentWidgetPosition; private readonly _onDidBlur = this._store.add(new Emitter()); readonly onDidBlur: Event = this._onDidBlur.event; @@ -148,13 +148,7 @@ export class InlineChatContentWidget implements IContentWidget { } getPosition(): IContentWidgetPosition | null { - if (!this._position) { - return null; - } - return { - position: this._position, - preference: [ContentWidgetPositionPreference.ABOVE] - }; + return this._position ?? null; } beforeRender(): IDimension | null { @@ -191,7 +185,7 @@ export class InlineChatContentWidget implements IContentWidget { return this._widget.inputEditor.getValue(); } - show(position: IPosition) { + show(position: IPosition, below: boolean) { if (!this._visible) { this._visible = true; this._focusNext = true; @@ -200,7 +194,11 @@ export class InlineChatContentWidget implements IContentWidget { const wordInfo = this._editor.getModel()?.getWordAtPosition(position); - this._position = wordInfo ? new Position(position.lineNumber, wordInfo.startColumn) : position; + this._position = { + position: wordInfo ? new Position(position.lineNumber, wordInfo.startColumn) : position, + preference: [below ? ContentWidgetPositionPreference.BELOW : ContentWidgetPositionPreference.ABOVE] + }; + this._editor.addContentWidget(this); this._widget.setContext(true); this._widget.setVisible(true); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 52477995683..f889ece2099 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -5,23 +5,28 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; import { Barrier, DeferredPromise, Queue } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { MovingAverage } from 'vs/base/common/numbers'; +import { isEqual } from 'vs/base/common/resources'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { ISelection, Selection } from 'vs/editor/common/core/selection'; +import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { TextEdit } from 'vs/editor/common/languages'; +import { IValidEditOperation } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; +import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -29,25 +34,21 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { showChatView } from 'vs/workbench/contrib/chat/browser/chat'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { IInlineChatSavingService } from './inlineChatSavingService'; -import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionPrompt, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IInlineChatSessionService } from './inlineChatSessionService'; -import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; -import { InlineChatZoneWidget } from './inlineChatZoneWidget'; -import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IValidEditOperation } from 'vs/editor/common/model'; -import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; -import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; -import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; -import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { isEqual } from 'vs/base/common/resources'; -import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IChatWidgetLocationOptions } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; +import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionPrompt, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; +import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { IInlineChatSavingService } from './inlineChatSavingService'; +import { IInlineChatSessionService } from './inlineChatSessionService'; +import { InlineChatZoneWidget } from './inlineChatZoneWidget'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -142,6 +143,7 @@ export class InlineChatController implements IEditorContribution { @IDialogService private readonly _dialogService: IDialogService, @IContextKeyService contextKeyService: IContextKeyService, @IChatService private readonly _chatService: IChatService, + @IEditorService private readonly _editorService: IEditorService, @INotebookEditorService notebookEditorService: INotebookEditorService, ) { this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); @@ -215,7 +217,7 @@ export class InlineChatController implements IEditorContribution { } })); - this._log('NEW controller'); + this._log(`NEW controller`); } dispose(): void { @@ -233,7 +235,7 @@ export class InlineChatController implements IEditorContribution { if (message instanceof Error) { this._logService.error(message, ...more); } else { - this._logService.trace(`[IE] (editor:${this._editor.getId()})${message}`, ...more); + this._logService.trace(`[IE] (editor:${this._editor.getId()}) ${message}`, ...more); } } @@ -271,6 +273,7 @@ export class InlineChatController implements IEditorContribution { } catch (error) { // this should not happen but when it does make sure to tear down the UI and everything + this._log('error during run', error); onUnexpectedError(error); if (this._session) { this._inlineChatSessionService.releaseSession(this._session); @@ -373,7 +376,7 @@ export class InlineChatController implements IEditorContribution { return State.INIT_UI; } - private async [State.INIT_UI](options: InlineChatRunOptions): Promise { + private async [State.INIT_UI](options: InlineChatRunOptions): Promise { assertType(this._session); assertType(this._strategy); @@ -399,11 +402,14 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.add(this._ui.value.content.onDidBlur(() => this.cancelSession())); this._ui.value.content.setSession(this._session); - // this._ui.value.zone.widget.updateSlashCommands(this._session.session.slashCommands ?? []); + this._ui.value.zone.widget.setChatModel(this._session.chatModel); this._updatePlaceholder(); - this._showWidget(!this._session.chatModel.hasRequests); + + const isModelEmpty = !this._session.chatModel.hasRequests; this._ui.value.zone.widget.updateToolbar(true); + this._ui.value.zone.widget.toggleStatus(!isModelEmpty); + this._showWidget(isModelEmpty); this._sessionStore.add(this._editor.onDidChangeModel((e) => { const msg = this._session?.chatModel.hasRequests @@ -456,6 +462,8 @@ export class InlineChatController implements IEditorContribution { } else if (options.isUnstashed) { delete options.isUnstashed; return State.SHOW_RESPONSE; + } else if (this._session.chatModel.requestInProgress) { + return State.SHOW_REQUEST; } else { return State.SHOW_RESPONSE; } @@ -527,7 +535,7 @@ export class InlineChatController implements IEditorContribution { } - private async [State.SHOW_REQUEST](): Promise { + private async [State.SHOW_REQUEST](): Promise { assertType(this._session); assertType(this._session.chatModel.requestInProgress); @@ -540,9 +548,9 @@ export class InlineChatController implements IEditorContribution { assertType(request.response); this._showWidget(false); - // this._ui.value.zone.widget.value = request.message.text; this._ui.value.zone.widget.selectAll(false); this._ui.value.zone.widget.updateInfo(''); + this._ui.value.zone.widget.toggleStatus(true); const { response } = request; const responsePromise = new DeferredPromise(); @@ -554,7 +562,8 @@ export class InlineChatController implements IEditorContribution { const progressiveEditsClock = StopWatch.create(); const progressiveEditsQueue = new Queue(); - let next: State.SHOW_RESPONSE | State.SHOW_REQUEST | State.CANCEL | State.PAUSE | State.ACCEPT | State.WAIT_FOR_INPUT = State.SHOW_RESPONSE; + let next: State.SHOW_RESPONSE | State.SHOW_REQUEST | State.CANCEL | State.PAUSE | State.ACCEPT = State.SHOW_RESPONSE; + store.add(Event.once(this._messages.event)(message => { this._log('state=_makeRequest) message received', message); this._chatService.cancelCurrentRequestForSession(chatModel.sessionId); @@ -567,7 +576,7 @@ export class InlineChatController implements IEditorContribution { } })); - store.add(chatModel.onDidChange(e => { + store.add(chatModel.onDidChange(async e => { if (e.kind === 'removeRequest' && e.requestId === request.id) { progressiveEditsCts.cancel(); responsePromise.complete(); @@ -576,6 +585,49 @@ export class InlineChatController implements IEditorContribution { } else { next = State.CANCEL; } + return; + } + if (e.kind === 'move') { + const log: typeof this._log = (msg: string, ...args: any[]) => this._log('state=_showRequest) moving inline chat', msg, ...args); + + log('move was requested', e.target, e.range); + + // if there's already a tab open for targetUri, show it and move inline chat to that tab + // otherwise, open the tab to the side + const editorPane = await this._editorService.openEditor({ resource: e.target }, SIDE_GROUP); + + if (!editorPane) { + log('opening editor failed'); + return; + } + + const newEditor = editorPane.getControl(); + if (!newEditor || !isCodeEditor(newEditor) || !newEditor.hasModel()) { + log('new editor is either missing or not a code editor or does not have a model'); + return; + } + + if (!this._session) { + log('controller does not have a session'); + return; + } + + const newSession = await this._inlineChatSessionService.createSession( + newEditor, + { + editMode: this._getMode(), + session: this._session, + }, + CancellationToken.None); // TODO@ulugbekna: add proper cancellation? + + const initialSelection = Selection.fromRange(Range.lift(e.range), SelectionDirection.LTR); + + InlineChatController.get(newEditor)?.run({ initialSelection, existingSession: newSession }); + + next = State.CANCEL; + responsePromise.complete(); + + return; } })); @@ -610,6 +662,9 @@ export class InlineChatController implements IEditorContribution { const edits = localEditGroup.edits; const newEdits = edits.slice(lastLength); if (newEdits.length > 0) { + + this._log(`${this._session?.textModelN.uri.toString()} received ${newEdits.length} edits`); + // NEW changes lastLength = edits.length; progressiveEditsAvgDuration.update(progressiveEditsClock.elapsed()); @@ -658,7 +713,6 @@ export class InlineChatController implements IEditorContribution { if (response.isCanceled) { - // await this._session.undoChangesUntil(response.requestId); } @@ -676,9 +730,10 @@ export class InlineChatController implements IEditorContribution { private async[State.SHOW_RESPONSE](): Promise { assertType(this._session); assertType(this._strategy); + assertType(this._session.lastExchange, `State ${State.SHOW_RESPONSE} should only be reached if there has been an exchange`); + assertType(this._session.lastExchange.response, `State ${State.SHOW_RESPONSE} should only be reached if last exchange had a response`); - const { response } = this._session.lastExchange!; - + const response = this._session.lastExchange.response; let newPosition: Position | undefined; @@ -809,17 +864,14 @@ export class InlineChatController implements IEditorContribution { if (this._ui.rawValue?.zone?.position) { this._ui.value.zone.updatePositionAndHeight(widgetPosition); - } else if (initialRender) { + } else if (initialRender && !this._configurationService.getValue(InlineChatConfigKeys.OnlyZoneWidget)) { const selection = this._editor.getSelection(); widgetPosition = selection.getStartPosition(); - this._ui.value.content.show(widgetPosition); + this._ui.value.content.show(widgetPosition, selection.isEmpty()); } else { this._ui.value.content.hide(); this._ui.value.zone.show(widgetPosition); - if (this._session) { - this._ui.value.zone.widget.setChatModel(this._session.chatModel); - } } return widgetPosition; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index cd8e3069846..0ebd530d2f4 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -12,13 +12,14 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from ' import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; export const CTX_INLINE_CHAT_EXPANSION = new RawContextKey('inlineChatExpansion', false, localize('inlineChatExpansion', "Whether the inline chat expansion is enabled when at the end of a just-typed line")); @@ -115,7 +116,7 @@ export class InlineChatExpandLineAction extends EditorAction2 { category: AbstractInlineChatAction.category, title: localize2('startWithCurrentLine', "Start in Editor with Current Line"), f1: true, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate()), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_HAS_AGENT, EditorContextKeys.writable), keybinding: { when: CTX_INLINE_CHAT_EXPANSION, weight: KeybindingWeight.EditorContrib, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 4d18fe88fce..bafa69df542 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -130,7 +130,7 @@ export class Session { private _lastInput: SessionPrompt | undefined; private _isUnstashed: boolean = false; - private readonly _exchanges: SessionExchange[] = []; + private readonly _exchanges: SessionExchange[]; private readonly _startTime = new Date(); private readonly _teldata: TelemetryData; @@ -154,6 +154,8 @@ export class Session { readonly wholeRange: SessionWholeRange, readonly hunkData: HunkData, readonly chatModel: ChatModel, + exchanges?: SessionExchange[], + lastInput?: SessionPrompt, ) { this.textModelNAltVersion = textModelN.getAlternativeVersionId(); this._teldata = { @@ -170,6 +172,8 @@ export class Session { discardedHunks: 0, responseTypes: '' }; + this._exchanges = exchanges ?? []; + this._lastInput = lastInput; } addInput(input: SessionPrompt): void { @@ -196,6 +200,10 @@ export class Session { // this._teldata.responseTypes += `${exchange.response instanceof ReplyResponse ? exchange.response.responseType : InlineChatResponseTypes.Empty}|`; } + get exchanges(): SessionExchange[] { + return this._exchanges; + } + get lastExchange(): SessionExchange | undefined { return this._exchanges[this._exchanges.length - 1]; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts index 6ab4ea5dbec..9b2246ad8f1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts @@ -2,17 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; -import { Event } from 'vs/base/common/event'; -import { EditMode } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IRange } from 'vs/editor/common/core/range'; -import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Session, StashedSession } from './inlineChatSession'; +import { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IRange } from 'vs/editor/common/core/range'; import { IValidEditOperation } from 'vs/editor/common/model'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { EditMode } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { Session, StashedSession } from './inlineChatSession'; export type Recording = { @@ -44,7 +44,7 @@ export interface IInlineChatSessionService { onDidStashSession: Event; onDidEndSession: Event; - createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise; + createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange; session?: Session }, token: CancellationToken): Promise; moveSession(session: Session, newEditor: ICodeEditor): void; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 44aecd6d917..fdbdc74e5b0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -16,6 +16,7 @@ import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/text import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -27,7 +28,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; import { IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer, Recording } from './inlineChatSessionService'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; type SessionData = { @@ -86,7 +86,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._sessions.clear(); } - async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise { + async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range; session?: Session }, token: CancellationToken): Promise { const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Editor); @@ -95,7 +95,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { return undefined; } - this._onWillStartSession.fire(editor); const textModel = editor.getModel(); @@ -104,15 +103,19 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const store = new DisposableStore(); this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${agent.extensionId}`); - const chatModel = this._chatService.startSession(ChatAgentLocation.Editor, token); + const chatModel = options.session?.chatModel ?? this._chatService.startSession(ChatAgentLocation.Editor, token); if (!chatModel) { this._logService.trace('[IE] NO chatModel found'); return undefined; } store.add(toDisposable(() => { - this._chatService.clearSession(chatModel.sessionId); - chatModel.dispose(); + const doesOtherSessionUseChatModel = [...this._sessions.values()].some(data => data.session !== session && data.session.chatModel === chatModel); + + if (!doesOtherSessionUseChatModel) { + this._chatService.clearSession(chatModel.sessionId); + chatModel.dispose(); + } })); const lastResponseListener = store.add(new MutableDisposable()); @@ -210,7 +213,9 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { agent, store.add(new SessionWholeRange(textModelN, wholeRange)), store.add(new HunkData(this._editorWorkerService, textModel0, textModelN)), - chatModel + chatModel, + options.session?.exchanges, // @ulugbekna: very hacky: we pass exchanges by reference because an exchange is added only on `addRequest` event from chat model which the migrated inline chat misses + options.session?.lastInput ); // store: key -> session diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 6a7a87d8f53..2bc98afe1a0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -351,6 +351,13 @@ export class InlineChatWidget { this._chatWidget.setInputPlaceholder(value); } + toggleStatus(show: boolean) { + this._elements.toolbar2.classList.toggle('hidden', !show); + this._elements.status.classList.toggle('hidden', !show); + this._elements.infoLabel.classList.toggle('hidden', !show); + this._onDidChangeHeight.fire(); + } + updateToolbar(show: boolean) { this._elements.root.classList.toggle('toolbar', show); this._elements.toolbar2.classList.toggle('hidden', !show); @@ -389,16 +396,6 @@ export class InlineChatWidget { this._chatWidget.setModel(chatModel, { inputValue: undefined }); } - - /** - * @deprecated use `setChatModel` instead - */ - addToHistory(input: string) { - if (this._chatWidget.viewModel?.model === this._defaultChatModel) { - this._chatWidget.input.acceptInput(true); - } - } - /** * @deprecated use `setChatModel` instead */ diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index 0395f2fe091..27eab929415 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -93,7 +93,8 @@ margin-left: auto; } -.monaco-workbench .inline-chat .status .label.hidden { +.monaco-workbench .inline-chat .status .label.hidden, +.monaco-workbench .inline-chat .status .label:empty { display: none; } @@ -123,15 +124,6 @@ } } -.monaco-workbench .inline-chat .status { - .actions.text-style { - display: none; - } - .actions.button-style { - display: inherit; - } -} - .monaco-workbench .inline-chat .status .actions > .monaco-button, .monaco-workbench .inline-chat .status .actions > .monaco-button-dropdown { margin-right: 4px; diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 7245babf43d..17e6fd8f081 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -16,6 +16,7 @@ export const enum InlineChatConfigKeys { Mode = 'inlineChat.mode', FinishOnType = 'inlineChat.finishOnType', AcceptedOrDiscardBeforeSave = 'inlineChat.acceptedOrDiscardBeforeSave', + OnlyZoneWidget = 'inlineChat.experimental.onlyZoneWidget', HoldToSpeech = 'inlineChat.holdToSpeech', AccessibleDiffView = 'inlineChat.accessibleDiffView' } @@ -64,7 +65,13 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('accessibleDiffView.on', "The accessible diff viewer is always enabled."), localize('accessibleDiffView.off', "The accessible diff viewer is never enabled."), ], - } + }, + [InlineChatConfigKeys.OnlyZoneWidget]: { + description: localize('onlyZone', "Whether inline chat opens directly as zone widget, between the lines, instead of the overlay widget which turns into a zone."), + default: false, + type: 'boolean', + tags: ['experimental'] + }, } }); diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 7a488364abc..b69ee2a6c21 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -54,9 +54,6 @@ import { CellEditType, CellKind, CellUri, INTERACTIVE_WINDOW_EDITOR_ID, Notebook import { InteractiveWindowOpen } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { executeReplInput } from 'vs/workbench/contrib/replNotebook/browser/repl.contribution'; -import { ReplEditor } from 'vs/workbench/contrib/replNotebook/browser/replEditor'; -import { ReplEditorInput } from 'vs/workbench/contrib/replNotebook/browser/replEditorInput'; import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -98,7 +95,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork providerDisplayName: 'Interactive Notebook', displayName: 'Interactive', filenamePattern: ['*.interactive'], - priority: RegisteredEditorPriority.exclusive + priority: RegisteredEditorPriority.builtin })); } @@ -477,9 +474,6 @@ registerAction2(class extends Action2 { { id: MenuId.InteractiveInputExecute }, - { - id: MenuId.ReplInputExecute - } ], icon: icons.executeIcon, f1: false, @@ -502,28 +496,21 @@ registerAction2(class extends Action2 { const historyService = accessor.get(IInteractiveHistoryService); const notebookEditorService = accessor.get(INotebookEditorService); let editorControl: { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; - let isReplEditor = false; if (context) { const resourceUri = URI.revive(context); const editors = editorService.findEditors(resourceUri); for (const found of editors) { - if (found.editor.typeId === ReplEditorInput.ID || found.editor.typeId === InteractiveEditorInput.ID) { + if (found.editor.typeId === InteractiveEditorInput.ID) { const editor = await editorService.openEditor(found.editor, found.groupId); editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; - isReplEditor = found.editor.typeId === ReplEditorInput.ID; break; } } } else { - const editor = editorService.activeEditorPane; - isReplEditor = editor instanceof ReplEditor; editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; } - if (editorControl && isReplEditor) { - executeReplInput(bulkEditService, historyService, notebookEditorService, editorControl); - } if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) { const notebookDocument = editorControl.notebookEditor.textModel; @@ -539,6 +526,7 @@ registerAction2(class extends Action2 { return; } + historyService.replaceLast(notebookDocument.uri, value); historyService.addToHistory(notebookDocument.uri, ''); textModel.setValue(''); @@ -644,6 +632,8 @@ registerAction2(class extends Action2 { const historyService = accessor.get(IInteractiveHistoryService); const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + + if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) { const notebookDocument = editorControl.notebookEditor.textModel; const textModel = editorControl.codeEditor.getModel(); @@ -697,9 +687,9 @@ registerAction2(class extends Action2 { const textModel = editorControl.codeEditor.getModel(); if (notebookDocument && textModel) { - const previousValue = historyService.getNextValue(notebookDocument.uri); - if (previousValue) { - textModel.setValue(previousValue); + const nextValue = historyService.getNextValue(notebookDocument.uri); + if (nextValue !== null) { + textModel.setValue(nextValue); } } } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index cbd701d383f..72d05495ba3 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -541,8 +541,11 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro this._widgetDisposableStore.add(editorModel.onDidChangeContent(() => { const value = editorModel.getValue(); - if (this.input?.resource && value !== '') { - (this.input as InteractiveEditorInput).historyService.replaceLast(this.input.resource, value); + if (this.input?.resource) { + const historyService = (this.input as InteractiveEditorInput).historyService; + if (!historyService.matchesCurrent(this.input.resource, value)) { + historyService.replaceLast(this.input.resource, value); + } } })); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts b/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts index cdfbf09592b..89fefcf0a35 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts @@ -14,6 +14,7 @@ export const IInteractiveHistoryService = createDecorator>(); } + matchesCurrent(uri: URI, value: string): boolean { + const history = this._history.get(uri); + if (!history) { + return false; + } + + return history.current() === value; + } + addToHistory(uri: URI, value: string): void { - if (!this._history.has(uri)) { + const history = this._history.get(uri); + if (!history) { this._history.set(uri, new HistoryNavigator2([value], 50)); return; } - const history = this._history.get(uri)!; - history.resetCursor(); - if (history?.current() !== value) { - history?.add(value); - } + history.add(value); } + getPreviousValue(uri: URI): string | null { const history = this._history.get(uri); return history?.previous() ?? null; @@ -57,16 +65,14 @@ export class InteractiveHistoryService extends Disposable implements IInteractiv } replaceLast(uri: URI, value: string) { - if (!this._history.has(uri)) { + const history = this._history.get(uri); + if (!history) { this._history.set(uri, new HistoryNavigator2([value], 50)); return; } else { - const history = this._history.get(uri); - if (history?.current() !== value) { - history?.replaceLast(value); - } + history.replaceLast(value); + history.resetCursor(); } - } clearHistory(uri: URI) { diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.esm.html b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.esm.html new file mode 100644 index 00000000000..5276ed507ba --- /dev/null +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.esm.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.esm.html b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.esm.html new file mode 100644 index 00000000000..2f87d2489ce --- /dev/null +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.esm.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions.ts index cc1137f6a7e..1e6544b55ca 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions.ts @@ -6,6 +6,14 @@ import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +/** + * Do not leave at 12, when at 12 and we have whitespace and only one line, + * then there's not enough space for the button `Show Whitespace Differences` + */ +export const fixedEditorPaddingSingleLineCells = { + top: 24, + bottom: 24 +}; export const fixedEditorPadding = { top: 12, bottom: 12 diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index c5668620255..62c7cc8567d 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DiffElementCellViewModelBase, getFormattedMetadataJSON, getFormattedOutputJSON, OutputComparison, outputEqual, OUTPUT_EDITOR_HEIGHT_MAGIC, PropertyFoldingState, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel, DiffElementPlaceholderViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; -import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DiffSide, DIFF_CELL_MARGIN, INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED, CellDiffPlaceholderRenderTemplate, IDiffCellMarginOverlay } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DiffSide, DIFF_CELL_MARGIN, INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED, CellDiffPlaceholderRenderTemplate, IDiffCellMarginOverlay, NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -38,7 +38,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { fixedDiffEditorOptions, fixedEditorOptions, fixedEditorPadding } from 'vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions'; +import { fixedDiffEditorOptions, fixedEditorOptions, fixedEditorPadding, fixedEditorPaddingSingleLineCells } from 'vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; @@ -46,6 +46,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel'; import { localize } from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; export function getOptimizedNestedCodeEditorWidgetOptions(): ICodeEditorWidgetOptions { return { @@ -309,6 +310,7 @@ abstract class AbstractElementRenderer extends Disposable { protected readonly menuService: IMenuService, protected readonly contextKeyService: IContextKeyService, protected readonly configurationService: IConfigurationService, + protected readonly textConfigurationService: ITextResourceConfigurationService ) { super(); // init @@ -545,7 +547,6 @@ abstract class AbstractElementRenderer extends Disposable { modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() }); - this._metadataEditorDisposeStore.add(this.updateEditorOptions(this._metadataEditor!, ['renderSideBySide', 'useInlineViewWhenSpaceIsLimited'])); this.layout({ metadataHeight: true }); this._metadataEditorDisposeStore.add(this._metadataEditor); @@ -659,7 +660,6 @@ abstract class AbstractElementRenderer extends Disposable { originalEditor: getOptimizedNestedCodeEditorWidgetOptions(), modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() }); - this._outputEditorDisposeStore.add(this.updateEditorOptions(this._outputEditor!, ['renderSideBySide', 'useInlineViewWhenSpaceIsLimited'])); this._outputEditorDisposeStore.add(this._outputEditor); this._outputEditorContainer?.classList.add('diff'); @@ -750,47 +750,6 @@ abstract class AbstractElementRenderer extends Disposable { abstract updateSourceEditor(): void; abstract layout(state: IDiffElementLayoutState): void; - - protected updateEditorOptions(editor: DiffEditorWidget, optionsToUpdate: ('hideUnchangedRegions' | 'renderSideBySide' | 'useInlineViewWhenSpaceIsLimited')[]): IDisposable { - return Disposable.None; - // if (!optionsToUpdate.length) { - // return Disposable.None; - // } - - // const options: { - // renderSideBySide?: boolean; - // useInlineViewWhenSpaceIsLimited?: boolean; - // hideUnchangedRegions?: { enabled: boolean }; - // } = {}; - - // if (optionsToUpdate.includes('renderSideBySide')) { - // options.renderSideBySide = this.configurationService.getValue('diffEditor.renderSideBySide'); - // } - // if (optionsToUpdate.includes('hideUnchangedRegions')) { - // const enabled = this.configurationService.getValue('diffEditor.hideUnchangedRegions.enabled'); - // options.hideUnchangedRegions = { enabled }; - // } - // if (optionsToUpdate.includes('useInlineViewWhenSpaceIsLimited')) { - // options.useInlineViewWhenSpaceIsLimited = this.configurationService.getValue('diffEditor.useInlineViewWhenSpaceIsLimited'); - // } - - // editor.updateOptions(options); - - // return this.configurationService.onDidChangeConfiguration(e => { - // if (e.affectsConfiguration('diffEditor.hideUnchangedRegions.enabled')) { - // const enabled = this.configurationService.getValue('diffEditor.hideUnchangedRegions.enabled'); - // editor.updateOptions({ hideUnchangedRegions: { enabled } }); - // } - // if (e.affectsConfiguration('diffEditor.renderSideBySide')) { - // const renderSideBySide = this.configurationService.getValue('diffEditor.renderSideBySide'); - // editor.updateOptions({ renderSideBySide }); - // } - // if (e.affectsConfiguration('diffEditor.useInlineViewWhenSpaceIsLimited')) { - // const useInlineViewWhenSpaceIsLimited = this.configurationService.getValue('diffEditor.useInlineViewWhenSpaceIsLimited'); - // editor.updateOptions({ useInlineViewWhenSpaceIsLimited }); - // } - // }); - } } abstract class SingleSideDiffElement extends AbstractElementRenderer { @@ -814,6 +773,7 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { menuService: IMenuService, contextKeyService: IContextKeyService, configurationService: IConfigurationService, + textConfigurationService: ITextResourceConfigurationService ) { super( notebookEditor, @@ -830,6 +790,7 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { menuService, contextKeyService, configurationService, + textConfigurationService ); this.cell = cell; this.templateData = templateData; @@ -1107,9 +1068,9 @@ export class DeletedElement extends SingleSideDiffElement { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, - + @ITextResourceConfigurationService textConfigurationService: ITextResourceConfigurationService, ) { - super(notebookEditor, cell, templateData, 'left', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'left', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService, textConfigurationService); } get nestedCellViewModel() { @@ -1239,8 +1200,9 @@ export class InsertElement extends SingleSideDiffElement { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, + @ITextResourceConfigurationService textConfigurationService: ITextResourceConfigurationService, ) { - super(notebookEditor, cell, templateData, 'right', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'right', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService, textConfigurationService); } get nestedCellViewModel() { return this.cell.modified!; @@ -1372,8 +1334,9 @@ export class ModifiedElement extends AbstractElementRenderer { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, + @ITextResourceConfigurationService textConfigurationService: ITextResourceConfigurationService, ) { - super(notebookEditor, cell, templateData, 'full', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'full', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService, textConfigurationService); this.cell = cell; this.templateData = templateData; this._editorViewStateChanged = false; @@ -1576,7 +1539,6 @@ export class ModifiedElement extends AbstractElementRenderer { modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() }); - this._register(this.updateEditorOptions(this._outputMetadataEditor, ['renderSideBySide', 'useInlineViewWhenSpaceIsLimited'])); this._register(this._outputMetadataEditor); const originalOutputMetadataSource = JSON.stringify(this.cell.original.outputs[0].metadata ?? {}, undefined, '\t'); const modifiedOutputMetadataSource = JSON.stringify(this.cell.modified.outputs[0].metadata ?? {}, undefined, '\t'); @@ -1662,6 +1624,15 @@ export class ModifiedElement extends AbstractElementRenderer { } this._editor = this.templateData.sourceEditor; + // If there is only 1 line, then ensure we have the necessary padding to display the button for whitespaces. + // E.g. assume we have a cell with 1 line and we add some whitespace, + // Then diff editor displays the button `Show Whitespace Differences`, however with 12 paddings on the top, the + // button can get cut off. + if (lineCount === 1) { + this._editor.updateOptions({ + padding: fixedEditorPaddingSingleLineCells + }); + } this._editor.layout({ width: this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN, height: editorHeight @@ -1695,29 +1666,31 @@ export class ModifiedElement extends AbstractElementRenderer { this._cellHeader.buildHeader(); renderSourceEditor(); - const scopedContextKeyService = this.contextKeyService.createScoped(this._cellHeaderContainer); + const scopedContextKeyService = this.contextKeyService.createScoped(this.templateData.inputToolbarContainer); this._register(scopedContextKeyService); const inputChanged = NOTEBOOK_DIFF_CELL_INPUT.bindTo(scopedContextKeyService); inputChanged.set(this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue()); + const ignoreWhitespace = NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE.bindTo(scopedContextKeyService); + const ignore = this.textConfigurationService.getValue(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace'); + ignoreWhitespace.set(ignore); + this._toolbar = this.templateData.toolbar; this._toolbar.context = { cell: this.cell }; - const actions: IAction[] = []; - const refreshToolbar = () => { + const ignore = this.textConfigurationService.getValue(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace'); + ignoreWhitespace.set(ignore); const hasChanges = this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue(); inputChanged.set(hasChanges); - if (!actions.length) { + if (hasChanges) { + const actions: IAction[] = []; const menu = this.menuService.getMenuActions(MenuId.NotebookDiffCellInputTitle, scopedContextKeyService, { shouldForwardArgs: true }); createAndFillInActionBarActions(menu, actions); - } - - if (hasChanges) { this._toolbar.setActions(actions); } else { this._toolbar.setActions([]); @@ -1725,6 +1698,12 @@ export class ModifiedElement extends AbstractElementRenderer { }; this._register(this.cell.modified.textModel.onDidChangeContent(() => refreshToolbar())); + this._register(this.textConfigurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(this.cell.modified.uri, 'diffEditor') && + e.affectedKeys.has('diffEditor.ignoreTrimWhitespace')) { + refreshToolbar(); + } + })); refreshToolbar(); } @@ -1746,7 +1725,7 @@ export class ModifiedElement extends AbstractElementRenderer { this._editor!.setModel({ original: textModel, - modified: modifiedTextModel + modified: modifiedTextModel, }); const handleViewStateChange = () => { @@ -1759,7 +1738,7 @@ export class ModifiedElement extends AbstractElementRenderer { } }; - this._register(this.updateEditorOptions(this._editor!, ['hideUnchangedRegions', 'renderSideBySide', 'useInlineViewWhenSpaceIsLimited'])); + this.updateEditorOptionsForWhitespace(); this._register(this._editor!.getOriginalEditor().onDidChangeCursorSelection(handleViewStateChange)); this._register(this._editor!.getOriginalEditor().onDidScrollChange(handleScrollChange)); this._register(this._editor!.getModifiedEditor().onDidChangeCursorSelection(handleViewStateChange)); @@ -1773,7 +1752,26 @@ export class ModifiedElement extends AbstractElementRenderer { const contentHeight = this._editor!.getContentHeight(); this.cell.editorHeight = contentHeight; } + private updateEditorOptionsForWhitespace() { + const editor = this._editor; + if (!editor) { + return; + } + const uri = editor.getModel()?.modified.uri || editor.getModel()?.original.uri; + if (!uri) { + return; + } + const ignoreTrimWhitespace = this.textConfigurationService.getValue(uri, 'diffEditor.ignoreTrimWhitespace'); + editor.updateOptions({ ignoreTrimWhitespace }); + this._register(this.textConfigurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(uri, 'diffEditor') && + e.affectedKeys.has('diffEditor.ignoreTrimWhitespace')) { + const ignoreTrimWhitespace = this.textConfigurationService.getValue(uri, 'diffEditor.ignoreTrimWhitespace'); + editor.updateOptions({ ignoreTrimWhitespace }); + } + })); + } layout(state: IDiffElementLayoutState) { DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { if (state.editorHeight && this._editor) { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index 11b4d887ba9..24e92aa2590 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -11,10 +11,10 @@ import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/com import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { DiffElementCellViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; -import { INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE_KEY, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/common/notebookDiffEditorInput'; -import { nextChangeIcon, openAsTextIcon, previousChangeIcon, renderOutputIcon, revertIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { nextChangeIcon, openAsTextIcon, previousChangeIcon, renderOutputIcon, revertIcon, toggleWhitespace } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -23,6 +23,7 @@ import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { CellEditType, NOTEBOOK_DIFF_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; // ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID) @@ -62,37 +63,6 @@ registerAction2(class extends Action2 { } }); -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'notebook.toggle.diff.renderSideBySide', - title: localize('inlineView', "Inline View"), - toggled: ContextKeyExpr.equals('config.diffEditor.renderSideBySide', false), - precondition: ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), - menu: [{ - id: MenuId.EditorTitle, - order: 0, - group: '1_diff', - when: ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID) - }] - }); - } - - async run(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - const activeEditor = editorService.activeEditorPane; - if (activeEditor && activeEditor instanceof NotebookTextDiffEditor) { - const diffEditorInput = activeEditor.input as NotebookDiffEditorInput; - if (diffEditorInput.resource) { - const configurationService = accessor.get(IConfigurationService); - - const oldValue = configurationService.getValue('diffEditor.renderSideBySide'); - configurationService.updateValue('diffEditor.renderSideBySide', !oldValue); - } - } - } -}); - registerAction2(class extends Action2 { constructor() { super( @@ -213,6 +183,38 @@ registerAction2(class extends Action2 { }); +registerAction2(class extends Action2 { + constructor() { + super( + { + id: 'notebook.toggle.diff.cell.ignoreTrimWhitespace', + title: localize('ignoreTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"), + icon: toggleWhitespace, + f1: false, + menu: { + id: MenuId.NotebookDiffCellInputTitle, + when: NOTEBOOK_DIFF_CELL_INPUT, + order: 1, + }, + precondition: NOTEBOOK_DIFF_CELL_INPUT, + toggled: ContextKeyExpr.equals(NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE_KEY, false), + } + ); + } + run(accessor: ServicesAccessor, context?: { cell: DiffElementCellViewModelBase }) { + const cell = context?.cell; + if (!cell?.modified) { + return; + } + const uri = cell.modified.uri; + const configService = accessor.get(ITextResourceConfigurationService); + const key = 'diffEditor.ignoreTrimWhitespace'; + const val = configService.getValue(uri, key); + configService.updateValue(uri, key, !val); + } +}); + + registerAction2(class extends Action2 { constructor() { super( @@ -223,7 +225,8 @@ registerAction2(class extends Action2 { f1: false, menu: { id: MenuId.NotebookDiffCellInputTitle, - when: NOTEBOOK_DIFF_CELL_INPUT + when: NOTEBOOK_DIFF_CELL_INPUT, + order: 2 }, precondition: NOTEBOOK_DIFF_CELL_INPUT diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index b54999fde7d..0b53f1cfb2c 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -141,6 +141,8 @@ export interface CellDiffViewModelLayoutChangeEvent extends IDiffElementSelfLayo export const DIFF_CELL_MARGIN = 16; export const NOTEBOOK_DIFF_CELL_INPUT = new RawContextKey('notebookDiffCellInputChanged', false); +export const NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE_KEY = 'notebookDiffCellIgnoreWhitespace'; +export const NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE = new RawContextKey(NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE_KEY, false); export const NOTEBOOK_DIFF_CELL_PROPERTY = new RawContextKey('notebookDiffCellPropertyChanged', false); export const NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED = new RawContextKey('notebookDiffCellPropertyExpanded', false); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index c0f4fcd898a..55910ef9136 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -248,7 +248,8 @@ export class CellDiffSideBySideRenderer implements IListRenderer { if (this._models.has(uri)) { throw new Error(`notebook for ${uri} already exists`); } - const notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, uri, data.cells, data.metadata, transientOptions); + + const info = await this.withNotebookDataProvider(viewType); + if (!(info instanceof SimpleNotebookProviderInfo)) { + throw new Error('CANNOT open file notebook with this provider'); + } + + + const bytes = stream ? await streamToBuffer(stream) : VSBuffer.fromByteArray([]); + const data = await info.serializer.dataToNotebook(bytes); + + + const notebookModel = this._instantiationService.createInstance(NotebookTextModel, info.viewType, uri, data.cells, data.metadata, info.serializer.options); const modelData = new ModelData(notebookModel, this._onWillDisposeDocument.bind(this)); this._models.set(uri, modelData); this._notebookDocumentService.addNotebookDocument(modelData); this._onWillAddNotebookDocument.fire(notebookModel); this._onDidAddNotebookDocument.fire(notebookModel); - this._postDocumentOpenActivation(viewType); + this._postDocumentOpenActivation(info.viewType); return notebookModel; } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts index 65980c57a9c..e643b5f0534 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts @@ -13,6 +13,7 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS import { NotebookEditorSimpleWorker } from 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker'; import { INotebookWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerHost'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; +import { FileAccess } from 'vs/base/common/network'; export class NotebookEditorWorkerServiceImpl extends Disposable implements INotebookEditorWorkerService { declare readonly _serviceBrand: undefined; @@ -207,7 +208,7 @@ class NotebookWorkerClient extends Disposable { constructor(private readonly _notebookService: INotebookService, label: string) { super(); - this._workerFactory = new DefaultWorkerFactory(label); + this._workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), label); this._worker = null; this._modelManager = null; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 7a851b42f9c..b6868371e5e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -356,7 +356,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { return true; } if (otherInput instanceof NotebookEditorInput) { - return this.viewType === otherInput.viewType && isEqual(this.resource, otherInput.resource); + return this.editorId === otherInput.editorId && isEqual(this.resource, otherInput.resource); } return false; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 90904bb4571..52bd9d3a7c6 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -377,19 +377,9 @@ export class NotebookFileWorkingCopyModelFactory implements IStoredFileWorkingCo async createModel(resource: URI, stream: VSBufferReadableStream, token: CancellationToken): Promise { - const info = await this._notebookService.withNotebookDataProvider(this._viewType); - if (!(info instanceof SimpleNotebookProviderInfo)) { - throw new Error('CANNOT open file notebook with this provider'); - } + const notebookModel = this._notebookService.getNotebookTextModel(resource) ?? + await this._notebookService.createNotebookTextModel(this._viewType, resource, stream); - const bytes = await streamToBuffer(stream); - const data = await info.serializer.dataToNotebook(bytes); - - if (token.isCancellationRequested) { - throw new CancellationError(); - } - - const notebookModel = this._notebookService.createNotebookTextModel(info.viewType, resource, data, info.serializer.options); return new NotebookFileWorkingCopyModel(notebookModel, this._notebookService, this._configurationService, this._telemetryService, this._notebookLogService); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts index dc3d26a0ca3..8231edfcdb9 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts @@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { IResolvedNotebookEditorModel, NotebookEditorModelCreationOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IReference } from 'vs/base/common/lifecycle'; import { Event, IWaitUntil } from 'vs/base/common/event'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; export const INotebookEditorModelResolverService = createDecorator('INotebookModelResolverService'); @@ -49,6 +50,8 @@ export interface INotebookEditorModelResolverService { isDirty(resource: URI): boolean; + createUntitledNotebookTextModel(viewType: string): Promise; + resolve(resource: URI, viewType?: string, creationOptions?: NotebookEditorModelCreationOptions): Promise>; resolve(resource: IUntitledNotebookResource, viewType: string, creationOtions?: NotebookEditorModelCreationOptions): Promise>; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts index 1ca105ea97b..f34fbcae53a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts @@ -177,46 +177,36 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes return this._data.isDirty(resource); } - async resolve(resource: URI, viewType?: string, options?: NotebookEditorModelCreationOptions): Promise>; - async resolve(resource: IUntitledNotebookResource, viewType: string, options: NotebookEditorModelCreationOptions): Promise>; - async resolve(arg0: URI | IUntitledNotebookResource, viewType?: string, options?: NotebookEditorModelCreationOptions): Promise> { - let resource: URI; - let hasAssociatedFilePath = false; - if (URI.isUri(arg0)) { - resource = arg0; - } else { - if (!arg0.untitledResource) { - const info = this._notebookService.getContributedNotebookType(assertIsDefined(viewType)); - if (!info) { - throw new Error('UNKNOWN view type: ' + viewType); - } + private createUntitledUri(notebookType: string) { + const info = this._notebookService.getContributedNotebookType(assertIsDefined(notebookType)); + if (!info) { + throw new Error('UNKNOWN notebook type: ' + notebookType); + } - const suffix = NotebookProviderInfo.possibleFileEnding(info.selectors) ?? ''; - for (let counter = 1; ; counter++) { - const candidate = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}${suffix}`, query: viewType }); - if (!this._notebookService.getNotebookTextModel(candidate)) { - resource = candidate; - break; - } - } - } else if (arg0.untitledResource.scheme === Schemas.untitled) { - resource = arg0.untitledResource; - } else { - resource = arg0.untitledResource.with({ scheme: Schemas.untitled }); - hasAssociatedFilePath = true; + const suffix = NotebookProviderInfo.possibleFileEnding(info.selectors) ?? ''; + for (let counter = 1; ; counter++) { + const candidate = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}${suffix}`, query: notebookType }); + if (!this._notebookService.getNotebookTextModel(candidate)) { + return candidate; } } + } - if (resource.scheme === CellUri.scheme) { - throw new Error(`CANNOT open a cell-uri as notebook. Tried with ${resource.toString()}`); + private async validateResourceViewType(uri: URI | undefined, viewType: string | undefined) { + if (!uri && !viewType) { + throw new Error('Must provide at least one of resource or viewType'); } - resource = this._uriIdentService.asCanonicalUri(resource); + if (uri?.scheme === CellUri.scheme) { + throw new Error(`CANNOT open a cell-uri as notebook. Tried with ${uri.toString()}`); + } - const existingViewType = this._notebookService.getNotebookTextModel(resource)?.viewType; + const resource = this._uriIdentService.asCanonicalUri(uri ?? this.createUntitledUri(viewType!)); + + const existingNotebook = this._notebookService.getNotebookTextModel(resource); if (!viewType) { - if (existingViewType) { - viewType = existingViewType; + if (existingNotebook) { + viewType = existingNotebook.viewType; } else { await this._extensionService.whenInstalledExtensionsRegistered(); const providers = this._notebookService.getContributedNotebookTypes(resource); @@ -230,9 +220,9 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes throw new Error(`Missing viewType for '${resource}'`); } - if (existingViewType && existingViewType !== viewType) { + if (existingNotebook && existingNotebook.viewType !== viewType) { - await this._onWillFailWithConflict.fireAsync({ resource, viewType }, CancellationToken.None); + await this._onWillFailWithConflict.fireAsync({ resource: resource, viewType }, CancellationToken.None); // check again, listener should have done cleanup const existingViewType2 = this._notebookService.getNotebookTextModel(resource)?.viewType; @@ -240,8 +230,34 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes throw new Error(`A notebook with view type '${existingViewType2}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`); } } + return { resource, viewType }; + } - const reference = this._data.acquire(resource.toString(), viewType, hasAssociatedFilePath, options?.limits, options?.scratchpad); + public async createUntitledNotebookTextModel(viewType: string) { + const resource = this._uriIdentService.asCanonicalUri(this.createUntitledUri(viewType)); + + return (await this._notebookService.createNotebookTextModel(viewType, resource)); + } + + async resolve(resource: URI, viewType?: string, options?: NotebookEditorModelCreationOptions): Promise>; + async resolve(resource: IUntitledNotebookResource, viewType: string, options: NotebookEditorModelCreationOptions): Promise>; + async resolve(arg0: URI | IUntitledNotebookResource, viewType?: string, options?: NotebookEditorModelCreationOptions): Promise> { + let resource: URI | undefined; + let hasAssociatedFilePath; + if (URI.isUri(arg0)) { + resource = arg0; + } else if (arg0.untitledResource) { + if (arg0.untitledResource.scheme === Schemas.untitled) { + resource = arg0.untitledResource; + } else { + resource = arg0.untitledResource.with({ scheme: Schemas.untitled }); + hasAssociatedFilePath = true; + } + } + + const validated = await this.validateResourceViewType(resource, viewType); + + const reference = this._data.acquire(validated.resource.toString(), validated.viewType, hasAssociatedFilePath, options?.limits, options?.scratchpad); try { const model = await reference.object; return { diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 50e49939035..0f7b65b847a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -12,7 +12,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { VSBuffer } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFileStatWithMetadata, IWriteFileOptions } from 'vs/platform/files/common/files'; import { ITextQuery } from 'vs/workbench/services/search/common/search'; @@ -79,7 +79,7 @@ export interface INotebookService { updateMimePreferredRenderer(viewType: string, mimeType: string, rendererId: string, otherMimetypes: readonly string[]): void; saveMimeDisplayOrder(target: ConfigurationTarget): void; - createNotebookTextModel(viewType: string, uri: URI, data: NotebookData, transientOptions: TransientOptions): NotebookTextModel; + createNotebookTextModel(viewType: string, uri: URI, stream?: VSBufferReadableStream): Promise; getNotebookTextModel(uri: URI): NotebookTextModel | undefined; getNotebookTextModels(): Iterable; listNotebookDocuments(): readonly NotebookTextModel[]; diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.esm.ts b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.esm.ts new file mode 100644 index 00000000000..d288a2ebd9d --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; +import { create } from './notebookSimpleWorker'; + +bootstrapSimpleWorker(create); diff --git a/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts b/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts index 3b82ad1889e..b9a82cf5584 100644 --- a/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts +++ b/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts @@ -9,7 +9,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { ILink } from 'vs/editor/common/languages'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/services/output/common/output'; -import { MonacoWebWorker, createWebWorker } from 'vs/editor/browser/services/webWorker'; +import { MonacoWebWorker, createWorkbenchWebWorker } from 'vs/editor/browser/services/webWorker'; import { ICreateData, OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; @@ -73,7 +73,7 @@ export class OutputLinkProvider extends Disposable { workspaceFolders: this.contextService.getWorkspace().folders.map(folder => folder.uri.toString()) }; - this.worker = createWebWorker(this.modelService, this.languageConfigurationService, { + this.worker = createWorkbenchWebWorker(this.modelService, this.languageConfigurationService, { moduleId: 'vs/workbench/contrib/output/common/outputLinkComputer', createData, label: 'outputLinkComputer' diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 73d7c2e286a..95dabc4d10d 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -67,10 +67,11 @@ import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumbe import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; +import { ISetting, ISettingsGroup, SETTINGS_AUTHORITY, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { getInvalidTypeError } from 'vs/workbench/services/preferences/common/preferencesValidation'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { URI } from 'vs/base/common/uri'; const $ = DOM.$; @@ -1377,6 +1378,8 @@ class SettingObjectRenderer extends AbstractSettingObjectRenderer implements ITr // If the key of the default value is changed, remove the default value if (e.originalItem.key.data !== e.newItem.key.data && settingSupportsRemoveDefault && e.originalItem.key.data in defaultValue) { newValue[e.originalItem.key.data] = null; + } else { + delete newValue[e.originalItem.key.data]; } newValue[e.newItem.key.data] = e.newItem.value.data; newItems.push(e.newItem); @@ -2114,6 +2117,7 @@ export class SettingTreeRenderers extends Disposable { new Separator(), this._instantiationService.createInstance(CopySettingIdAction), this._instantiationService.createInstance(CopySettingAsJSONAction), + this._instantiationService.createInstance(CopySettingAsURLAction), ]; const actionFactory = (setting: ISetting, settingTarget: SettingsTarget) => this.getActionsForSetting(setting, settingTarget); @@ -2599,6 +2603,29 @@ class CopySettingAsJSONAction extends Action { } } +class CopySettingAsURLAction extends Action { + static readonly ID = 'settings.copySettingAsURL'; + static readonly LABEL = localize('copySettingAsURLLabel', "Copy Setting as URL"); + + constructor( + @IClipboardService private readonly clipboardService: IClipboardService, + @IProductService private readonly productService: IProductService, + ) { + super(CopySettingAsURLAction.ID, CopySettingAsURLAction.LABEL); + } + + override async run(context: SettingsTreeSettingElement): Promise { + if (context) { + const settingKey = context.setting.key; + const product = this.productService.urlProtocol; + const uri = URI.from({ scheme: product, authority: SETTINGS_AUTHORITY, path: `/${settingKey}` }, true); + await this.clipboardService.writeText(uri.toString()); + } + + return Promise.resolve(undefined); + } +} + class SyncSettingAction extends Action { static readonly ID = 'settings.stopSyncingSetting'; static readonly LABEL = localize('stopSyncingSetting', "Sync This Setting"); diff --git a/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts b/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts index 618c875f813..fb27af59137 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts @@ -6,10 +6,10 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; -import { EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { EditorExtensions, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor'; import { parse } from 'vs/base/common/marshalling'; import { assertType } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { CellEditType, CellKind, NotebookSetting, NotebookWorkingCopyTypeIdentifier, REPL_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -23,11 +23,7 @@ import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; import { extname, isEqual } from 'vs/base/common/resources'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { localize2 } from 'vs/nls'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; -import { Schemas } from 'vs/base/common/network'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; @@ -44,6 +40,11 @@ import { getReplView } from 'vs/workbench/contrib/debug/browser/repl'; import { REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { localize2 } from 'vs/nls'; +import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; type SerializedNotebookEditorData = { resource: URI; preferredResource: URI; viewType: string; options?: NotebookEditorInputOptions }; class ReplEditorSerializer implements IEditorSerializer { @@ -98,7 +99,6 @@ export class ReplDocumentContribution extends Disposable implements IWorkbenchCo constructor( @INotebookService notebookService: INotebookService, @IEditorResolverService editorResolverService: IEditorResolverService, - @IEditorService editorService: IEditorService, @INotebookEditorModelResolverService private readonly notebookEditorModelResolverService: INotebookEditorModelResolverService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService @@ -106,16 +106,16 @@ export class ReplDocumentContribution extends Disposable implements IWorkbenchCo super(); editorResolverService.registerEditor( - `*.replNotebook`, + `*.ipynb`, { id: 'repl', label: 'repl Editor', - priority: RegisteredEditorPriority.option + priority: RegisteredEditorPriority.builtin }, { - canSupportResource: uri => - (uri.scheme === Schemas.untitled && extname(uri) === '.replNotebook') || - (uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.replNotebook'), + // We want to support all notebook types which could have any file extension, + // so we just check if the resource corresponds to a notebook + canSupportResource: uri => notebookService.getNotebookTextModel(uri) !== undefined, singlePerResource: true }, { @@ -129,6 +129,9 @@ export class ReplDocumentContribution extends Disposable implements IWorkbenchCo ref.dispose(); }); return { editor: this.instantiationService.createInstance(ReplEditorInput, resource!), options }; + }, + createEditorInput: async ({ resource, options }) => { + return { editor: this.instantiationService.createInstance(ReplEditorInput, resource), options }; } } ); @@ -181,26 +184,79 @@ class ReplWindowWorkingCopyEditorHandler extends Disposable implements IWorkbenc registerWorkbenchContribution2(ReplWindowWorkingCopyEditorHandler.ID, ReplWindowWorkingCopyEditorHandler, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ReplDocumentContribution.ID, ReplDocumentContribution, WorkbenchPhase.BlockRestore); - registerAction2(class extends Action2 { constructor() { super({ - id: 'repl.newRepl', - title: localize2('repl.editor.open', 'New REPL Editor'), - category: 'Create', + id: 'repl.execute', + title: localize2('repl.execute', 'Execute REPL input'), + category: 'REPL', + keybinding: [{ + when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.repl'), + primary: KeyMod.CtrlCmd | KeyCode.Enter, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }, { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('activeEditor', 'workbench.editor.repl'), + ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', true) + ), + primary: KeyMod.Shift | KeyCode.Enter, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }, { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('activeEditor', 'workbench.editor.repl'), + ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', false) + ), + primary: KeyCode.Enter, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }], + menu: [ + { + id: MenuId.ReplInputExecute + } + ], + icon: icons.executeIcon, + f1: false, + metadata: { + description: 'Execute the Contents of the Input Box', + args: [ + { + name: 'resource', + description: 'Interactive resource Uri', + isOptional: true + } + ] + } }); } - async run(accessor: ServicesAccessor) { - const resource = URI.from({ scheme: Schemas.untitled, path: 'repl.replNotebook' }); - const editorInput: IUntypedEditorInput = { resource, options: { override: 'repl' } }; - + async run(accessor: ServicesAccessor, context?: UriComponents): Promise { const editorService = accessor.get(IEditorService); - await editorService.openEditor(editorInput, 1); + const bulkEditService = accessor.get(IBulkEditService); + const historyService = accessor.get(IInteractiveHistoryService); + const notebookEditorService = accessor.get(INotebookEditorService); + let editorControl: { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + if (context) { + const resourceUri = URI.revive(context); + const editors = editorService.findEditors(resourceUri); + for (const found of editors) { + if (found.editor.typeId === ReplEditorInput.ID) { + const editor = await editorService.openEditor(found.editor, found.groupId); + editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + break; + } + } + } + else { + editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + } + + if (editorControl) { + executeReplInput(bulkEditService, historyService, notebookEditorService, editorControl); + } } }); -export async function executeReplInput( +async function executeReplInput( bulkEditService: IBulkEditService, historyService: IInteractiveHistoryService, notebookEditorService: INotebookEditorService, @@ -220,7 +276,8 @@ export async function executeReplInput( return; } - historyService.addToHistory(notebookDocument.uri, value); + historyService.replaceLast(notebookDocument.uri, value); + historyService.addToHistory(notebookDocument.uri, ''); textModel.setValue(''); notebookDocument.cells[index].resetTextBuffer(textModel.getTextBuffer()); diff --git a/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts b/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts index 6696a03f28d..fca8f947569 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts @@ -4,19 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/interactive'; -import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; -import { ICodeEditorViewState, IDecorationOptions } from 'vs/editor/common/editorCommon'; +import { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneScrollPosition, IEditorPaneSelectionChangeEvent, IEditorPaneWithScrolling } from 'vs/workbench/common/editor'; @@ -56,15 +54,15 @@ import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { isEqual } from 'vs/base/common/resources'; import { NotebookFindContrib } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget'; -import { EXECUTE_REPL_COMMAND_ID, REPL_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { REPL_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import 'vs/css!./interactiveEditor'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { deepClone } from 'vs/base/common/objects'; import { MarginHoverController } from 'vs/editor/contrib/hover/browser/marginHoverController'; import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; import { ReplEditorInput } from 'vs/workbench/contrib/replNotebook/browser/replEditorInput'; +import { ReplInputHintContentWidget } from 'vs/workbench/contrib/interactive/browser/replInputHintContentWidget'; -const DECORATION_KEY = 'interactiveInputDecoration'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; const INPUT_CELL_VERTICAL_PADDING = 8; @@ -108,6 +106,7 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { private _editorMemento: IEditorMemento; private readonly _groupListener = this._register(new MutableDisposable()); private _runbuttonToolbar: ToolBar | undefined; + private _hintElement: ReplInputHintContentWidget | undefined; private _onDidFocusWidget = this._register(new Emitter()); override get onDidFocus(): Event { return this._onDidFocusWidget.event; } @@ -164,8 +163,7 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { this._notebookOptions = instantiationService.createInstance(NotebookOptions, this.window, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); - codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); - this._register(this._keybindingService.onDidUpdateKeybindings(this._updateInputDecoration, this)); + this._register(this._keybindingService.onDidUpdateKeybindings(this._updateInputHint, this)); this._register(notebookExecutionStateService.onDidChangeExecution((e) => { if (e.type === NotebookExecutionType.cell && isEqual(e.notebook, this._notebookWidget.value?.viewModel?.notebookDocument.uri)) { const cell = this._notebookWidget.value?.getCellByHandle(e.cellHandle); @@ -482,13 +480,13 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { this._widgetDisposableStore.add(this.themeService.onDidColorThemeChange(() => { if (this.isVisible()) { - this._updateInputDecoration(); + this._updateInputHint(); } })); this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => { if (this.isVisible()) { - this._updateInputDecoration(); + this._updateInputHint(); } })); @@ -525,12 +523,16 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { this._widgetDisposableStore.add(editorModel.onDidChangeContent(() => { const value = editorModel.getValue(); if (this.input?.resource && value !== '') { - (this.input as ReplEditorInput).historyService.replaceLast(this.input.resource, value); + const historyService = (this.input as ReplEditorInput).historyService; + if (!historyService.matchesCurrent(this.input.resource, value)) { + historyService.replaceLast(this.input.resource, value); + } } })); this._widgetDisposableStore.add(this._notebookWidget.value!.onDidScroll(() => this._onDidChangeScroll.fire())); + this._updateInputHint(); this._syncWithKernel(); } @@ -588,8 +590,6 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { NOTEBOOK_KERNEL.bindTo(this._contextKeyService).set(selectedOrSuggested.id); } } - - this._updateInputDecoration(); } layout(dimension: DOM.Dimension, position: DOM.IDomPosition): void { @@ -629,46 +629,22 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { return new DOM.Dimension(Math.max(0, width), Math.max(0, height)); } - private _updateInputDecoration(): void { + private _updateInputHint(): void { if (!this._codeEditorWidget) { return; } - if (!this._codeEditorWidget.hasModel()) { - return; + const shouldHide = + !this._codeEditorWidget.hasModel() || + this._configurationService.getValue(InteractiveWindowSetting.showExecutionHint) === false || + this._codeEditorWidget.getModel()!.getValueLength() !== 0; + + if (!this._hintElement && !shouldHide) { + this._hintElement = this._instantiationService.createInstance(ReplInputHintContentWidget, this._codeEditorWidget); + } else if (this._hintElement && shouldHide) { + this._hintElement.dispose(); + this._hintElement = undefined; } - - const model = this._codeEditorWidget.getModel(); - - const decorations: IDecorationOptions[] = []; - - if (model?.getValueLength() === 0) { - const transparentForeground = resolveColorValue(editorForeground, this.themeService.getColorTheme())?.transparent(0.4); - const languageId = model.getLanguageId(); - if (languageId !== 'plaintext') { - const keybinding = this._keybindingService.lookupKeybinding(EXECUTE_REPL_COMMAND_ID, this._contextKeyService)?.getLabel(); - const text = keybinding ? - nls.localize('interactiveInputPlaceHolder', "Type '{0}' code here and press {1} to run", languageId, keybinding) : - nls.localize('interactiveInputPlaceHolderNoKeybinding', "Type '{0}' code here and click run", languageId); - decorations.push({ - range: { - startLineNumber: 0, - endLineNumber: 0, - startColumn: 0, - endColumn: 1 - }, - renderOptions: { - after: { - contentText: text, - color: transparentForeground ? transparentForeground.toString() : undefined - } - } - }); - } - - } - - this._codeEditorWidget.setDecorationsByType('interactive-decoration', DECORATION_KEY, decorations); } getScrollPosition(): IEditorPaneScrollPosition { @@ -703,6 +679,8 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { this._notebookWidget.value.onWillHide(); } } + + this._updateInputHint(); } override clearInput() { diff --git a/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts b/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts index bf82d590a26..206b9471e7e 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts @@ -5,6 +5,7 @@ import { IReference } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -12,9 +13,10 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { EditorInputCapabilities } from 'vs/workbench/common/editor'; +import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -28,6 +30,7 @@ export class ReplEditorInput extends NotebookEditorInput { private inputModelRef: IReference | undefined; private isScratchpad: boolean; + private label: string; private isDisposing = false; constructor( @@ -44,18 +47,37 @@ export class ReplEditorInput extends NotebookEditorInput { @ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService, @IInteractiveHistoryService public readonly historyService: IInteractiveHistoryService, @ITextModelService private readonly _textModelService: ITextModelService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IBulkEditService private readonly bulkEditService: IBulkEditService ) { super(resource, undefined, 'jupyter-notebook', {}, _notebookService, _notebookModelResolverService, _fileDialogService, labelService, fileService, filesConfigurationService, extensionService, editorService, textResourceConfigurationService, customEditorLabelService); - this.isScratchpad = configurationService.getValue(NotebookSetting.InteractiveWindowPromptToSave) !== true; + this.isScratchpad = resource.scheme === 'untitled' && configurationService.getValue(NotebookSetting.InteractiveWindowPromptToSave) !== true; + this.label = this.createEditorLabel(resource); + } + + private createEditorLabel(resource: URI | undefined): string { + if (!resource) { + return 'REPL'; + } + + const match = new RegExp('Untitled-(\\d+)\.').exec(resource.path); + if (match?.length === 2) { + return `REPL - ${match[1]}`; + } + const filename = resource.path.split('/').pop(); + return filename ? `REPL - ${filename}` : 'REPL'; } override get typeId(): string { return ReplEditorInput.ID; } + override get editorId(): string | undefined { + return 'repl'; + } + override getName() { - return 'REPL'; + return this.label; } override get capabilities() { @@ -67,12 +89,59 @@ export class ReplEditorInput extends NotebookEditorInput { | scratchPad; } + private async ensureInputBoxCell(notebook: NotebookTextModel) { + let lastCell = notebook.cells[notebook.cells.length - 1]; + + if (!lastCell || lastCell.cellKind === CellKind.Markup || lastCell.getValue().trim() !== '') { + // ensure we have an empty cell at the end for the input box + await this.bulkEditService.apply([ + new ResourceNotebookCellEdit(notebook.uri, + { + editType: CellEditType.Replace, + index: notebook.cells.length, + count: 0, + cells: [{ + cellKind: CellKind.Code, + mime: undefined, + language: 'python', + source: '', + outputs: [], + metadata: {} + }] + } + ) + ]); + + // Or directly on the notebook? + // notebook.applyEdits([ + // { + // editType: CellEditType.Replace, + // index: notebook.cells.length, + // count: 0, + // cells: [ + // { + // cellKind: CellKind.Code, + // language: 'python', + // mime: undefined, + // outputs: [], + // source: '' + // } + // ] + // } + // ], true, undefined, () => undefined, undefined, false); + + lastCell = notebook.cells[notebook.cells.length - 1]; + } + + return lastCell; + } + async resolveInput(notebook: NotebookTextModel) { if (this.inputModelRef) { return this.inputModelRef.object.textEditorModel; } + const lastCell = await this.ensureInputBoxCell(notebook); - const lastCell = notebook.cells[notebook.cells.length - 1]; this.inputModelRef = await this._textModelService.createModelReference(lastCell.uri); return this.inputModelRef.object.textEditorModel; } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 1da6d744da9..93b067123e0 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -334,6 +334,11 @@ configurationRegistry.registerConfiguration({ default: 1, markdownDescription: nls.localize('search.searchEditor.defaultNumberOfContextLines', "The default number of surrounding context lines to use when creating new Search Editors. If using `#search.searchEditor.reusePriorSearchConfiguration#`, this can be set to `null` (empty) to use the prior Search Editor's configuration.") }, + 'search.searchEditor.focusResultsOnSearch': { + type: 'boolean', + default: false, + markdownDescription: nls.localize('search.searchEditor.focusResultsOnSearch', "When a search is triggered, focus the Search Editor results instead of the Search Editor input.") + }, 'search.sortOrder': { type: 'string', enum: [SearchSortOrder.Default, SearchSortOrder.FileNames, SearchSortOrder.Type, SearchSortOrder.Modified, SearchSortOrder.CountDescending, SearchSortOrder.CountAscending], diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 555b33faa98..c5ff5ece3bc 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -484,6 +484,15 @@ export class SearchEditor extends AbstractTextCodeEditor } async triggerSearch(_options?: { resetCursor?: boolean; delay?: number; focusResults?: boolean }) { + const focusResults = this.searchConfig.searchEditor.focusResultsOnSearch; + + // If _options don't define focusResult field, then use the setting + if (_options === undefined) { + _options = { focusResults: focusResults }; + } else if (_options.focusResults === undefined) { + _options.focusResults = focusResults; + } + const options = { resetCursor: true, delay: 0, ..._options }; if (!(this.queryEditorWidget.searchInput?.inputBox.isInputValid())) { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 5ab5da3f9e6..970ea589887 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -219,6 +219,6 @@ export const createEditorFromSearchResult = } else { const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { from: 'rawData', resultsContents: '', config: { ...config, contextLines } }); const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; - editor.triggerSearch({ focusResults: true }); + editor.triggerSearch(); } }; diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 90b3fb50616..a722f75a79a 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -206,7 +206,7 @@ export class DesktopMain extends Disposable { logService.info('workbench#open()'); // marking workbench open helps to diagnose flaky integration/smoke tests } if (logService.getLevel() === LogLevel.Trace) { - logService.trace('workbench#open(): with configuration', safeStringify(this.configuration)); + logService.trace('workbench#open(): with configuration', safeStringify({ ...this.configuration, nls: undefined /* exclude large property */ })); } // Shared Process diff --git a/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts b/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts index fdb19bc0870..14d76fe7272 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts @@ -143,7 +143,7 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth // * Extension id: The extension that has a preference // * Provider id: The provider that the preference is for // * The scopes: The subset of sessions that the preference applies to - const key = `${extensionId}-${providerId}-${session.scopes.join(' ')}`; + const key = `${extensionId}-${providerId}-${session.scopes.join(SCOPESLIST_SEPARATOR)}`; // Store the preference in the workspace and application storage. This allows new workspaces to // have a preference set already to limit the number of prompts that are shown... but also allows @@ -157,7 +157,7 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth // * Extension id: The extension that has a preference // * Provider id: The provider that the preference is for // * The scopes: The subset of sessions that the preference applies to - const key = `${extensionId}-${providerId}-${scopes.join(' ')}`; + const key = `${extensionId}-${providerId}-${scopes.join(SCOPESLIST_SEPARATOR)}`; // If a preference is set in the workspace, use that. Otherwise, use the global preference. return this.storageService.get(key, StorageScope.WORKSPACE) ?? this.storageService.get(key, StorageScope.APPLICATION); @@ -168,7 +168,7 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth // * Extension id: The extension that has a preference // * Provider id: The provider that the preference is for // * The scopes: The subset of sessions that the preference applies to - const key = `${extensionId}-${providerId}-${scopes.join(' ')}`; + const key = `${extensionId}-${providerId}-${scopes.join(SCOPESLIST_SEPARATOR)}`; // This won't affect any other workspaces that have a preference set, but it will remove the preference // for this workspace and the global preference. This is only paired with a call to updateSessionPreference... diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index ca86cea6dbc..02a4d6d344d 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -12,7 +12,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { canceled, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { COI, FileAccess } from 'vs/base/common/network'; +import { AppResourcePath, COI, FileAccess } from 'vs/base/common/network'; import * as platform from 'vs/base/common/platform'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -86,7 +86,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost const suffix = `?${suffixSearchParams.toString()}`; - const iframeModulePath = 'vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html'; + const iframeModulePath: AppResourcePath = `vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.${isESM ? 'esm.' : ''}html`; if (platform.isWeb) { const webEndpointUrlTemplate = this._productService.webEndpointUrlTemplate; const commit = this._productService.commit; @@ -189,7 +189,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost type: event.data.type, data: { baseUrl, - workerUrl: isESM ? FileAccess.asBrowserUri(factoryModuleId).toString(true) : require.toUrl(factoryModuleId), + workerUrl: isESM ? FileAccess.asBrowserUri('vs/workbench/api/worker/extensionHostWorker.esm.js').toString(true) : require.toUrl(factoryModuleId), fileRoot: globalThis._VSCODE_FILE_ROOT, nls: { messages: globalThis._VSCODE_NLS_MESSAGES, diff --git a/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html b/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html new file mode 100644 index 00000000000..bd4ea195337 --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html @@ -0,0 +1,164 @@ + + + + + + + + + diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.esm.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.esm.ts new file mode 100644 index 00000000000..46e5fb85eba --- /dev/null +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.esm.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LanguageDetectionSimpleWorker } from './languageDetectionSimpleWorker'; +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; +import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; + +bootstrapSimpleWorker(host => new LanguageDetectionSimpleWorker(host, () => { return {}; })); diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts index f967fc38127..ad75f10f039 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type { ModelOperations, ModelResult } from '@vscode/vscode-languagedetection'; +import { importAMDNodeModule } from 'vs/amdX'; import { StopWatch } from 'vs/base/common/stopwatch'; import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; @@ -103,7 +104,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { } const uri: string = await this._host.fhr('getRegexpModelUri', []); try { - this._regexpModel = await import(uri) as RegexpModel; + this._regexpModel = await importAMDNodeModule(uri, '') as RegexpModel; return this._regexpModel; } catch (e) { this._regexpLoadFailed = true; @@ -137,7 +138,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { } const uri: string = await this._host.fhr('getIndexJsUri', []); - const { ModelOperations } = await import(uri) as typeof import('@vscode/vscode-languagedetection'); + const { ModelOperations } = await importAMDNodeModule(uri, '') as typeof import('@vscode/vscode-languagedetection'); this._modelOperations = new ModelOperations({ modelJsonLoaderFunc: async () => { const response = await fetch(await this._host.fhr('getModelJsonUri', [])); diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index 1577e10f794..29256414c6c 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -24,6 +24,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { LRUCache } from 'vs/base/common/map'; import { ILogService } from 'vs/platform/log/common/log'; +import { canASAR } from 'vs/base/common/amd'; const TOP_LANG_COUNTS = 12; @@ -66,21 +67,22 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet ) { super(); + const useAsar = canASAR && this._environmentService.isBuilt && !isWeb; this._languageDetectionWorkerClient = this._register(new LanguageDetectionWorkerClient( modelService, languageService, telemetryService, - // TODO: See if it's possible to bundle vscode-languagedetection - this._environmentService.isBuilt && !isWeb + // TODO@esm: See if it's possible to bundle vscode-languagedetection + useAsar ? FileAccess.asBrowserUri(`${moduleLocationAsar}/dist/lib/index.js`).toString(true) : FileAccess.asBrowserUri(`${moduleLocation}/dist/lib/index.js`).toString(true), - this._environmentService.isBuilt && !isWeb + useAsar ? FileAccess.asBrowserUri(`${moduleLocationAsar}/model/model.json`).toString(true) : FileAccess.asBrowserUri(`${moduleLocation}/model/model.json`).toString(true), - this._environmentService.isBuilt && !isWeb + useAsar ? FileAccess.asBrowserUri(`${moduleLocationAsar}/model/group1-shard1of1.bin`).toString(true) : FileAccess.asBrowserUri(`${moduleLocation}/model/group1-shard1of1.bin`).toString(true), - this._environmentService.isBuilt && !isWeb + useAsar ? FileAccess.asBrowserUri(`${regexpModuleLocationAsar}/dist/index.js`).toString(true) : FileAccess.asBrowserUri(`${regexpModuleLocation}/dist/index.js`).toString(true), languageConfigurationService @@ -234,7 +236,7 @@ export class LanguageDetectionWorkerClient extends EditorWorkerClient { private readonly _regexpModelUri: string, languageConfigurationService: ILanguageConfigurationService, ) { - super(modelService, true, 'languageDetectionWorkerService', languageConfigurationService); + super(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, true, 'languageDetectionWorkerService', languageConfigurationService); } private _getOrCreateLanguageDetectionWorker(): Promise> { diff --git a/src/vs/workbench/services/search/browser/searchService.ts b/src/vs/workbench/services/search/browser/searchService.ts index f9aea8ee65a..7c8570a4622 100644 --- a/src/vs/workbench/services/search/browser/searchService.ts +++ b/src/vs/workbench/services/search/browser/searchService.ts @@ -21,7 +21,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { ILocalFileSearchSimpleWorker, ILocalFileSearchSimpleWorkerHost } from 'vs/workbench/services/search/common/localFileSearchWorkerTypes'; import { memoize } from 'vs/base/common/decorators'; import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; -import { Schemas } from 'vs/base/common/network'; +import { FileAccess, Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; @@ -64,7 +64,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe ) { super(); this._worker = null; - this._workerFactory = new DefaultWorkerFactory('localFileSearchWorker'); + this._workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), 'localFileSearchWorker'); } sendTextSearchMatch(match: IFileMatch, queryId: number): void { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 69c36258113..5a21173e42e 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -434,6 +434,7 @@ export interface ISearchConfigurationProperties { singleClickBehaviour: 'default' | 'peekDefinition'; reusePriorSearchConfiguration: boolean; defaultNumberOfContextLines: number | null; + focusResultsOnSearch: boolean; experimental: {}; }; sortOrder: SearchSortOrder; diff --git a/src/vs/workbench/services/search/worker/localFileSearch.esm.ts b/src/vs/workbench/services/search/worker/localFileSearch.esm.ts new file mode 100644 index 00000000000..fadd727c58f --- /dev/null +++ b/src/vs/workbench/services/search/worker/localFileSearch.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; +import { create } from 'vs/workbench/services/search/worker/localFileSearch'; + +bootstrapSimpleWorker(create); diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts index ee9f5a9f1ca..1a54e0d1bb6 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts @@ -128,23 +128,15 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { } private async _createWorkerProxy(): Promise { - const textmateModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-textmate`; - const textmateModuleLocationAsar: AppResourcePath = `${nodeModulesAsarPath}/vscode-textmate`; const onigurumaModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-oniguruma`; const onigurumaModuleLocationAsar: AppResourcePath = `${nodeModulesAsarPath}/vscode-oniguruma`; const useAsar = canASAR && this._environmentService.isBuilt && !isWeb; - const textmateLocation: AppResourcePath = useAsar ? textmateModuleLocationAsar : textmateModuleLocation; const onigurumaLocation: AppResourcePath = useAsar ? onigurumaModuleLocationAsar : onigurumaModuleLocation; - const textmateMain: AppResourcePath = `${textmateLocation}/release/main.js`; - const onigurumaMain: AppResourcePath = `${onigurumaLocation}/release/main.js`; const onigurumaWASM: AppResourcePath = `${onigurumaLocation}/release/onig.wasm`; - const uri = FileAccess.asBrowserUri(textmateMain).toString(true); const createData: ICreateData = { grammarDefinitions: this._grammarDefinitions, - textmateMainUri: uri, - onigurumaMainUri: FileAccess.asBrowserUri(onigurumaMain).toString(true), onigurumaWASMUri: FileAccess.asBrowserUri(onigurumaWASM).toString(true), }; const host: ITextMateWorkerHost = { diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts index 3974d642732..28a58f6837d 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts @@ -11,6 +11,7 @@ import { ICreateGrammarResult, TMGrammarFactory } from 'vs/workbench/services/te import { IValidEmbeddedLanguagesMap, IValidGrammarDefinition, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import type { IOnigLib, IRawTheme, StackDiff } from 'vscode-textmate'; import { TextMateWorkerTokenizer } from './textMateWorkerTokenizer'; +import { importAMDNodeModule } from 'vs/amdX'; /** * Defines the worker entry point. Must be exported and named `create`. @@ -27,8 +28,6 @@ export interface ITextMateWorkerHost { export interface ICreateData { grammarDefinitions: IValidGrammarDefinitionDTO[]; - textmateMainUri: string; - onigurumaMainUri: string; onigurumaWASMUri: string; } @@ -78,9 +77,8 @@ export class TextMateTokenizationWorker { } private async _loadTMGrammarFactory(grammarDefinitions: IValidGrammarDefinition[]): Promise { - const uri = this._createData.textmateMainUri; - const vscodeTextmate = await import(uri); - const vscodeOniguruma = await import(this._createData.onigurumaMainUri); + const vscodeTextmate = await importAMDNodeModule('vscode-textmate', 'release/main.js'); + const vscodeOniguruma = await importAMDNodeModule('vscode-oniguruma', 'release/main.js'); const response = await fetch(this._createData.onigurumaWASMUri); // Using the response directly only works if the server sets the MIME type 'application/wasm'. diff --git a/src/vs/workbench/services/textfile/common/encoding.ts b/src/vs/workbench/services/textfile/common/encoding.ts index 75d739662e8..537ec42d3dc 100644 --- a/src/vs/workbench/services/textfile/common/encoding.ts +++ b/src/vs/workbench/services/textfile/common/encoding.ts @@ -322,7 +322,7 @@ const IGNORE_ENCODINGS = ['ascii', 'utf-16', 'utf-32']; */ async function guessEncodingByBuffer(buffer: VSBuffer, candidateGuessEncodings?: string[]): Promise { - // TODO@bpasero ESM: this used to be `dist/jschardet.min.js`, but we are running into an issue that + // TODO@bpasero TODO@esm: this used to be `dist/jschardet.min.js`, but we are running into an issue that // https://github.com/aadsm/jschardet/pull/96 mitigates. Long-term we should just add minification // of dependencies into our build process so that we do not depend on how others are doing it. const jschardet = await importAMDNodeModule('jschardet', isESM ? 'dist/jschardet.js' : 'dist/jschardet.min.js'); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index f8d24e3f5fd..9e2c97b4a68 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -129,7 +129,7 @@ import { ContextViewService } from 'vs/platform/contextview/browser/contextViewS import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IListService, ListService } from 'vs/platform/list/browser/listService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService'; +import { WorkbenchEditorWorkerService } from 'vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService'; import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsService'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markerDecorations'; import { IMarkerService } from 'vs/platform/markers/common/markers'; @@ -154,7 +154,7 @@ registerSingleton(IExtensionStorageService, ExtensionStorageService, Instantiati registerSingleton(IExtensionGalleryService, ExtensionGalleryService, InstantiationType.Delayed); registerSingleton(IContextViewService, ContextViewService, InstantiationType.Delayed); registerSingleton(IListService, ListService, InstantiationType.Delayed); -registerSingleton(IEditorWorkerService, EditorWorkerService, InstantiationType.Eager /* registers link detection and word based suggestions for any document */); +registerSingleton(IEditorWorkerService, WorkbenchEditorWorkerService, InstantiationType.Eager /* registers link detection and word based suggestions for any document */); registerSingleton(IMarkerDecorationsService, MarkerDecorationsService, InstantiationType.Delayed); registerSingleton(IMarkerService, MarkerService, InstantiationType.Delayed); registerSingleton(IContextKeyService, ContextKeyService, InstantiationType.Delayed); diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index 3c29ebef0a4..17e4d7aa002 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -70,7 +70,7 @@ declare module 'vscode' { constructor(value: Uri, license: string, snippet: string); } - export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseDetectedParticipantPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2; + export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseDetectedParticipantPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart; export class ChatResponseWarningPart { value: MarkdownString; @@ -121,6 +121,14 @@ declare module 'vscode' { }, options?: { status?: { description: string; kind: ChatResponseReferencePartStatusKind } }); } + export class ChatResponseMovePart { + + readonly uri: Uri; + readonly range: Range; + + constructor(uri: Uri, range: Range); + } + export interface ChatResponseStream { /** diff --git a/src/vscode-dts/vscode.proposed.notebookReplDocument.d.ts b/src/vscode-dts/vscode.proposed.notebookReplDocument.d.ts new file mode 100644 index 00000000000..5330758f7ef --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookReplDocument.d.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface NotebookDocumentShowOptions { + /** + * The notebook should be opened in a REPL editor, + * where the last cell of the notebook is an input box and the rest are read-only. + */ + readonly asRepl?: boolean; + } +} diff --git a/test/monaco/dist/core.html b/test/monaco/dist/core.html index 35f656becf1..13a208944b1 100644 --- a/test/monaco/dist/core.html +++ b/test/monaco/dist/core.html @@ -5,6 +5,9 @@
+ diff --git a/test/unit/assert-esm.js b/test/unit/assert-esm.js new file mode 100644 index 00000000000..170c31382c6 --- /dev/null +++ b/test/unit/assert-esm.js @@ -0,0 +1,498 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +// UTILITY + +// Object.create compatible in IE +const create = Object.create || function (p) { + if (!p) { throw Error('no type'); } + function f() { } + f.prototype = p; + return new f(); + }; + + // UTILITY + var util = { + inherits: function (ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }, + isArray: function (ar) { + return Array.isArray(ar); + }, + isBoolean: function (arg) { + return typeof arg === 'boolean'; + }, + isNull: function (arg) { + return arg === null; + }, + isNullOrUndefined: function (arg) { + return arg == null; + }, + isNumber: function (arg) { + return typeof arg === 'number'; + }, + isString: function (arg) { + return typeof arg === 'string'; + }, + isSymbol: function (arg) { + return typeof arg === 'symbol'; + }, + isUndefined: function (arg) { + return arg === undefined; + }, + isRegExp: function (re) { + return util.isObject(re) && util.objectToString(re) === '[object RegExp]'; + }, + isObject: function (arg) { + return typeof arg === 'object' && arg !== null; + }, + isDate: function (d) { + return util.isObject(d) && util.objectToString(d) === '[object Date]'; + }, + isError: function (e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); + }, + isFunction: function (arg) { + return typeof arg === 'function'; + }, + isPrimitive: function (arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; + }, + objectToString: function (o) { + return Object.prototype.toString.call(o); + } + }; + + const pSlice = Array.prototype.slice; + + // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys + const Object_keys = typeof Object.keys === 'function' ? Object.keys : (function () { + const hasOwnProperty = Object.prototype.hasOwnProperty, + hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'), + dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ], + dontEnumsLength = dontEnums.length; + + return function (obj) { + if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { + throw new TypeError('Object.keys called on non-object'); + } + + let result = [], prop, i; + + for (prop in obj) { + if (hasOwnProperty.call(obj, prop)) { + result.push(prop); + } + } + + if (hasDontEnumBug) { + for (i = 0; i < dontEnumsLength; i++) { + if (hasOwnProperty.call(obj, dontEnums[i])) { + result.push(dontEnums[i]); + } + } + } + return result; + }; + })(); + + // 1. The assert module provides functions that throw + // AssertionError's when particular conditions are not met. The + // assert module must conform to the following interface. + + const assert = ok; + + // 2. The AssertionError is defined in assert. + // new assert.AssertionError({ message: message, + // actual: actual, + // expected: expected }) + + assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + const stackStartFunction = options.stackStartFunction || fail; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } else { + // try to throw an error now, and from the stack property + // work out the line that called in to assert.js. + try { + this.stack = (new Error).stack.toString(); + } catch (e) { } + } + }; + + // assert.AssertionError instanceof Error + util.inherits(assert.AssertionError, Error); + + function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; + } + + function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } + } + + function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); + } + + // At present only the three keys mentioned above are used and + // understood by the spec. Implementations or sub modules can pass + // other keys to the AssertionError's constructor - they will be + // ignored. + + // 3. All of the following functions must throw an AssertionError + // when a corresponding condition is not met, with a message that + // may be undefined if not provided. All assertion methods provide + // both the actual and expected values to the assertion error for + // display purposes. + + export function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); + } + + // EXTENSION! allows for well behaved errors defined elsewhere. + assert.fail = fail; + + // 4. Pure assertion tests whether a value is truthy, as determined + // by !!guard. + // assert.ok(guard, message_opt); + // This statement is equivalent to assert.equal(true, !!guard, + // message_opt);. To test strictly for the value true, use + // assert.strictEqual(true, guard, message_opt);. + + export function ok(value, message) { + if (!value) { fail(value, true, message, '==', assert.ok); } + } + assert.ok = ok; + + // 5. The equality assertion tests shallow, coercive equality with + // ==. + // assert.equal(actual, expected, message_opt); + + assert.equal = function equal(actual, expected, message) { + if (actual != expected) { fail(actual, expected, message, '==', assert.equal); } + }; + + // 6. The non-equality assertion tests for whether two objects are not equal + // with != assert.notEqual(actual, expected, message_opt); + + assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } + }; + + // 7. The equivalence assertion tests a deep equality relation. + // assert.deepEqual(actual, expected, message_opt); + + assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected, false)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } + }; + + assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) { + if (!_deepEqual(actual, expected, true)) { + fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual); + } + }; + + function _deepEqual(actual, expected, strict) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + // } else if (actual instanceof Buffer && expected instanceof Buffer) { + // return compare(actual, expected) === 0; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if ((actual === null || typeof actual !== 'object') && + (expected === null || typeof expected !== 'object')) { + return strict ? actual === expected : actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected, strict); + } + } + + function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; + } + + function objEquiv(a, b, strict) { + if (a === null || a === undefined || b === null || b === undefined) { return false; } + // if one is a primitive, the other must be same + if (util.isPrimitive(a) || util.isPrimitive(b)) { return a === b; } + if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { return false; } + const aIsArgs = isArguments(a), + bIsArgs = isArguments(b); + if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) { return false; } + if (aIsArgs) { + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b, strict); + } + let ka = Object.keys(a), + kb = Object.keys(b), + key, i; + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length !== kb.length) { return false; } + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] !== kb[i]) { return false; } + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key], strict)) { return false; } + } + return true; + } + + // 8. The non-equivalence assertion tests for any deep inequality. + // assert.notDeepEqual(actual, expected, message_opt); + + assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected, false)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } + }; + + assert.notDeepStrictEqual = notDeepStrictEqual; + export function notDeepStrictEqual(actual, expected, message) { + if (_deepEqual(actual, expected, true)) { + fail(actual, expected, message, 'notDeepStrictEqual', notDeepStrictEqual); + } + } + + + // 9. The strict equality assertion tests strict equality, as determined by ===. + // assert.strictEqual(actual, expected, message_opt); + + assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } + }; + + // 10. The strict non-equality assertion tests for strict inequality, as + // determined by !==. assert.notStrictEqual(actual, expected, message_opt); + + assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } + }; + + function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; + } + + function _throws(shouldThrow, block, expected, message) { + let actual; + + if (typeof block !== 'function') { + throw new TypeError('block must be a function'); + } + + if (typeof expected === 'string') { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } + } + + // 11. Expected to throw an error: + // assert.throws(block, Error_opt, message_opt); + + assert.throws = function (block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); + }; + + // EXTENSION! This is annoying to write outside this module. + assert.doesNotThrow = function (block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); + }; + + assert.ifError = function (err) { if (err) { throw err; } }; + + function checkIsPromise(obj) { + return (obj !== null && typeof obj === 'object' && + typeof obj.then === 'function' && + typeof obj.catch === 'function'); + } + + const NO_EXCEPTION_SENTINEL = {}; + async function waitForActual(promiseFn) { + let resultPromise; + if (typeof promiseFn === 'function') { + // Return a rejected promise if `promiseFn` throws synchronously. + resultPromise = promiseFn(); + // Fail in case no promise is returned. + if (!checkIsPromise(resultPromise)) { + throw new Error('ERR_INVALID_RETURN_VALUE: promiseFn did not return Promise. ' + resultPromise); + } + } else if (checkIsPromise(promiseFn)) { + resultPromise = promiseFn; + } else { + throw new Error('ERR_INVALID_ARG_TYPE: promiseFn is not Function or Promise. ' + promiseFn); + } + + try { + await resultPromise; + } catch (e) { + return e; + } + return NO_EXCEPTION_SENTINEL; + } + + function expectsError(shouldHaveError, actual, message) { + if (shouldHaveError && actual === NO_EXCEPTION_SENTINEL) { + fail(undefined, 'Error', `Missing expected rejection${message ? ': ' + message : ''}`) + } else if (!shouldHaveError && actual !== NO_EXCEPTION_SENTINEL) { + fail(actual, undefined, `Got unexpected rejection (${actual.message})${message ? ': ' + message : ''}`) + } + } + + assert.rejects = async function rejects(promiseFn, message) { + expectsError(true, await waitForActual(promiseFn), message); + }; + + assert.doesNotReject = async function doesNotReject(fn, message) { + expectsError(false, await waitForActual(fn), message); + }; + + // ESM export + export default assert; + export const AssertionError = assert.AssertionError + // export const fail = assert.fail + // export const ok = assert.ok + export const equal = assert.equal + export const notEqual = assert.notEqual + export const deepEqual = assert.deepEqual + export const deepStrictEqual = assert.deepStrictEqual + export const notDeepEqual = assert.notDeepEqual + // export const notDeepStrictEqual = assert.notDeepStrictEqual + export const strictEqual = assert.strictEqual + export const notStrictEqual = assert.notStrictEqual + export const throws = assert.throws + export const doesNotThrow = assert.doesNotThrow + export const ifError = assert.ifError + export const rejects = assert.rejects + export const doesNotReject = assert.doesNotReject diff --git a/test/unit/browser/index.esm.js b/test/unit/browser/index.esm.js new file mode 100644 index 00000000000..b1d350f363c --- /dev/null +++ b/test/unit/browser/index.esm.js @@ -0,0 +1,421 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check +'use strict'; + +const path = require('path'); +const glob = require('glob'); +const events = require('events'); +const mocha = require('mocha'); +const createStatsCollector = require('../../../node_modules/mocha/lib/stats-collector'); +const MochaJUnitReporter = require('mocha-junit-reporter'); +const url = require('url'); +const minimatch = require('minimatch'); +const fs = require('fs'); +const playwright = require('@playwright/test'); +const { applyReporter } = require('../reporter'); +const yaserver = require('yaserver'); +const http = require('http'); +const { randomBytes } = require('crypto'); +const minimist = require('minimist'); +const { promisify } = require('node:util'); + +/** + * @type {{ + * run: string; + * grep: string; + * runGlob: string; + * browser: string; + * reporter: string; + * 'reporter-options': string; + * tfs: string; + * build: boolean; + * debug: boolean; + * sequential: boolean; + * help: boolean; + * }} +*/ +const args = minimist(process.argv.slice(2), { + boolean: ['build', 'debug', 'sequential', 'help'], + string: ['run', 'grep', 'runGlob', 'browser', 'reporter', 'reporter-options', 'tfs'], + default: { + build: false, + browser: ['chromium', 'firefox', 'webkit'], + reporter: process.platform === 'win32' ? 'list' : 'spec', + 'reporter-options': '' + }, + alias: { + grep: ['g', 'f'], + runGlob: ['glob', 'runGrep'], + debug: ['debug-browser'], + help: 'h' + }, + describe: { + build: 'run with build output (out-build)', + run: 'only run tests matching ', + grep: 'only run tests matching ', + debug: 'do not run browsers headless', + sequential: 'only run suites for a single browser at a time', + browser: 'browsers in which tests should run', + reporter: 'the mocha reporter', + 'reporter-options': 'the mocha reporter options', + tfs: 'tfs', + help: 'show the help' + } +}); + +if (args.help) { + console.log(`Usage: node ${process.argv[1]} [options] + +Options: +--build run with build output (out-build) +--run only run tests matching +--grep, -g, -f only run tests matching +--debug, --debug-browser do not run browsers headless +--sequential only run suites for a single browser at a time +--browser browsers in which tests should run +--reporter the mocha reporter +--reporter-options the mocha reporter options +--tfs tfs +--help, -h show the help`); + process.exit(0); +} + +const isDebug = !!args.debug; + +const withReporter = (function () { + if (args.tfs) { + { + return (browserType, runner) => { + new mocha.reporters.Spec(runner); + new MochaJUnitReporter(runner, { + reporterOptions: { + testsuitesTitle: `${args.tfs} ${process.platform}`, + mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${browserType}-${args.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined + } + }); + }; + } + } else { + return (_, runner) => applyReporter(runner, args); + } +})(); + +const outdir = args.build ? 'out-build' : 'out'; +const rootDir = path.resolve(__dirname, '..', '..', '..'); +const out = path.join(rootDir, `${outdir}`); + +function ensureIsArray(a) { + return Array.isArray(a) ? a : [a]; +} + +const testModules = (async function () { + + const excludeGlob = '**/{node,electron-sandbox,electron-main}/**/*.test.js'; + let isDefaultModules = true; + let promise; + + if (args.run) { + // use file list (--run) + isDefaultModules = false; + promise = Promise.resolve(ensureIsArray(args.run).map(file => { + file = file.replace(/^src/, 'out'); + file = file.replace(/\.ts$/, '.js'); + return path.relative(out, file); + })); + + } else { + // glob patterns (--glob) + const defaultGlob = '**/*.test.js'; + const pattern = args.runGlob || defaultGlob; + isDefaultModules = pattern === defaultGlob; + + promise = new Promise((resolve, reject) => { + glob(pattern, { cwd: out }, (err, files) => { + if (err) { + reject(err); + } else { + resolve(files); + } + }); + }); + } + + return promise.then(files => { + const modules = []; + for (const file of files) { + if (!minimatch(file, excludeGlob)) { + modules.push(file.replace(/\.js$/, '')); + + } else if (!isDefaultModules) { + console.warn(`DROPPONG ${file} because it cannot be run inside a browser`); + } + } + return modules; + }); +})(); + +function consoleLogFn(msg) { + const type = msg.type(); + const candidate = console[type]; + if (candidate) { + return candidate; + } + + if (type === 'warning') { + return console.warn; + } + + return console.log; +} + +async function createServer() { + // Demand a prefix to avoid issues with other services on the + // machine being able to access the test server. + const prefix = '/' + randomBytes(16).toString('hex'); + const serveStatic = await yaserver.createServer({ rootDir }); + + /** Handles a request for a remote method call, invoking `fn` and returning the result */ + const remoteMethod = async (req, response, fn) => { + const params = await new Promise((resolve, reject) => { + const body = []; + req.on('data', chunk => body.push(chunk)); + req.on('end', () => resolve(JSON.parse(Buffer.concat(body).toString()))); + req.on('error', reject); + }); + try { + const result = await fn(...params); + response.writeHead(200, { 'Content-Type': 'application/json' }); + response.end(JSON.stringify(result)); + } catch (err) { + response.writeHead(500); + response.end(err.message); + } + }; + + const server = http.createServer((request, response) => { + if (!request.url?.startsWith(prefix)) { + return response.writeHead(404).end(); + } + + // rewrite the URL so the static server can handle the request correctly + request.url = request.url.slice(prefix.length); + + function massagePath(p) { + // TODO@jrieken FISHY but it enables snapshot + // in ESM browser tests + p = String(p).replace(/\\/g, '/').replace(prefix, rootDir); + return p; + } + + switch (request.url) { + case '/remoteMethod/__readFileInTests': + return remoteMethod(request, response, p => fs.promises.readFile(massagePath(p), 'utf-8')); + case '/remoteMethod/__writeFileInTests': + return remoteMethod(request, response, (p, contents) => fs.promises.writeFile(massagePath(p), contents)); + case '/remoteMethod/__readDirInTests': + return remoteMethod(request, response, p => fs.promises.readdir(massagePath(p))); + case '/remoteMethod/__unlinkInTests': + return remoteMethod(request, response, p => fs.promises.unlink(massagePath(p))); + case '/remoteMethod/__mkdirPInTests': + return remoteMethod(request, response, p => fs.promises.mkdir(massagePath(p), { recursive: true })); + default: + return serveStatic.handle(request, response); + } + }); + + return new Promise((resolve, reject) => { + server.listen(0, 'localhost', () => { + resolve({ + dispose: () => server.close(), + // @ts-ignore + url: `http://localhost:${server.address().port}${prefix}` + }); + }); + server.on('error', reject); + }); +} + +async function runTestsInBrowser(testModules, browserType) { + const server = await createServer(); + const browser = await playwright[browserType].launch({ headless: !Boolean(args.debug), devtools: Boolean(args.debug) }); + const context = await browser.newContext(); + const page = await context.newPage(); + const target = new URL(server.url + '/test/unit/browser/renderer.esm.html'); + target.searchParams.set('baseUrl', url.pathToFileURL(path.join(rootDir, 'src')).toString()); + if (args.build) { + target.searchParams.set('build', 'true'); + } + if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + target.searchParams.set('ci', 'true'); + } + + // append CSS modules as query-param + await promisify(require('glob'))('**/*.css', { cwd: out }).then(async cssModules => { + const cssData = await new Response((await new Response(cssModules.join(',')).blob()).stream().pipeThrough(new CompressionStream('gzip'))).arrayBuffer(); + target.searchParams.set('_devCssData', Buffer.from(cssData).toString('base64')); + }); + + const emitter = new events.EventEmitter(); + await page.exposeFunction('mocha_report', (type, data1, data2) => { + emitter.emit(type, data1, data2); + }); + + await page.goto(target.href); + + if (args.build) { + const nlsMessages = await fs.promises.readFile(path.join(out, 'nls.messages.json'), 'utf8'); + await page.evaluate(value => { + // when running from `out-build`, ensure to load the default + // messages file, because all `nls.localize` calls have their + // english values removed and replaced by an index. + // @ts-ignore + globalThis._VSCODE_NLS_MESSAGES = JSON.parse(value); + }, nlsMessages); + } + + page.on('console', async msg => { + consoleLogFn(msg)(msg.text(), await Promise.all(msg.args().map(async arg => await arg.jsonValue()))); + }); + + withReporter(browserType, new EchoRunner(emitter, browserType.toUpperCase())); + + // collection failures for console printing + const failingModuleIds = []; + const failingTests = []; + emitter.on('fail', (test, err) => { + failingTests.push({ title: test.fullTitle, message: err.message }); + + if (err.stack) { + const regex = /(vs\/.*\.test)\.js/; + for (const line of String(err.stack).split('\n')) { + const match = regex.exec(line); + if (match) { + failingModuleIds.push(match[1]); + return; + } + } + } + }); + + try { + // @ts-expect-error + await page.evaluate(opts => loadAndRun(opts), { + modules: testModules, + grep: args.grep, + }); + } catch (err) { + console.error(err); + } + if (!isDebug) { + server?.dispose(); + await browser.close(); + } + + if (failingTests.length > 0) { + let res = `The followings tests are failing:\n - ${failingTests.map(({ title, message }) => `${title} (reason: ${message})`).join('\n - ')}`; + + if (failingModuleIds.length > 0) { + res += `\n\nTo DEBUG, open ${browserType.toUpperCase()} and navigate to ${target.href}?${failingModuleIds.map(module => `m=${module}`).join('&')}`; + } + + return `${res}\n`; + } +} + +class EchoRunner extends events.EventEmitter { + + constructor(event, title = '') { + super(); + createStatsCollector(this); + event.on('start', () => this.emit('start')); + event.on('end', () => this.emit('end')); + event.on('suite', (suite) => this.emit('suite', EchoRunner.deserializeSuite(suite, title))); + event.on('suite end', (suite) => this.emit('suite end', EchoRunner.deserializeSuite(suite, title))); + event.on('test', (test) => this.emit('test', EchoRunner.deserializeRunnable(test))); + event.on('test end', (test) => this.emit('test end', EchoRunner.deserializeRunnable(test))); + event.on('hook', (hook) => this.emit('hook', EchoRunner.deserializeRunnable(hook))); + event.on('hook end', (hook) => this.emit('hook end', EchoRunner.deserializeRunnable(hook))); + event.on('pass', (test) => this.emit('pass', EchoRunner.deserializeRunnable(test))); + event.on('fail', (test, err) => this.emit('fail', EchoRunner.deserializeRunnable(test, title), EchoRunner.deserializeError(err))); + event.on('pending', (test) => this.emit('pending', EchoRunner.deserializeRunnable(test))); + } + + static deserializeSuite(suite, titleExtra) { + return { + root: suite.root, + suites: suite.suites, + tests: suite.tests, + title: titleExtra && suite.title ? `${suite.title} - /${titleExtra}/` : suite.title, + titlePath: () => suite.titlePath, + fullTitle: () => suite.fullTitle, + timeout: () => suite.timeout, + retries: () => suite.retries, + slow: () => suite.slow, + bail: () => suite.bail + }; + } + + static deserializeRunnable(runnable, titleExtra) { + return { + title: runnable.title, + fullTitle: () => titleExtra && runnable.fullTitle ? `${runnable.fullTitle} - /${titleExtra}/` : runnable.fullTitle, + titlePath: () => runnable.titlePath, + async: runnable.async, + slow: () => runnable.slow, + speed: runnable.speed, + duration: runnable.duration, + currentRetry: () => runnable.currentRetry, + }; + } + + static deserializeError(err) { + const inspect = err.inspect; + err.inspect = () => inspect; + return err; + } +} + +testModules.then(async modules => { + + // run tests in selected browsers + const browserTypes = Array.isArray(args.browser) + ? args.browser : [args.browser]; + + let messages = []; + let didFail = false; + + try { + if (args.sequential) { + for (const browserType of browserTypes) { + messages.push(await runTestsInBrowser(modules, browserType)); + } + } else { + messages = await Promise.all(browserTypes.map(async browserType => { + return await runTestsInBrowser(modules, browserType); + })); + } + } catch (err) { + console.error(err); + if (!isDebug) { + process.exit(1); + } + } + + // aftermath + for (const msg of messages) { + if (msg) { + didFail = true; + console.log(msg); + } + } + if (!isDebug) { + process.exit(didFail ? 1 : 0); + } + +}).catch(err => { + console.error(err); +}); diff --git a/test/unit/browser/renderer.esm.html b/test/unit/browser/renderer.esm.html new file mode 100644 index 00000000000..8fb11a47095 --- /dev/null +++ b/test/unit/browser/renderer.esm.html @@ -0,0 +1,247 @@ + + + + + VSCode Tests + + + + +
+ + + + + + + + + + + diff --git a/test/unit/electron/index.esm.js b/test/unit/electron/index.esm.js new file mode 100644 index 00000000000..40f52df5062 --- /dev/null +++ b/test/unit/electron/index.esm.js @@ -0,0 +1,358 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check +'use strict'; + +// mocha disables running through electron by default. Note that this must +// come before any mocha imports. +process.env.MOCHA_COLORS = '1'; + +const { app, BrowserWindow, ipcMain, crashReporter, session } = require('electron'); +const product = require('../../../product.json'); +const { tmpdir } = require('os'); +const { existsSync, mkdirSync } = require('fs'); +const path = require('path'); +const mocha = require('mocha'); +const events = require('events'); +const MochaJUnitReporter = require('mocha-junit-reporter'); +const url = require('url'); +const net = require('net'); +const createStatsCollector = require('mocha/lib/stats-collector'); +const { applyReporter, importMochaReporter } = require('../reporter'); + +const minimist = require('minimist'); + +/** + * @type {{ + * grep: string; + * run: string; + * runGlob: string; + * dev: boolean; + * reporter: string; + * 'reporter-options': string; + * 'waitServer': string; + * timeout: string; + * 'crash-reporter-directory': string; + * tfs: string; + * build: boolean; + * coverage: boolean; + * coveragePath: string; + * coverageFormats: string | string[]; + * 'per-test-coverage': boolean; + * help: boolean; + * }} + */ +const args = minimist(process.argv.slice(2), { + string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs', 'coveragePath', 'coverageFormats'], + boolean: ['build', 'coverage', 'help', 'dev', 'per-test-coverage'], + alias: { + 'grep': ['g', 'f'], + 'runGlob': ['glob', 'runGrep'], + 'dev': ['dev-tools', 'devTools'], + 'help': 'h' + }, + default: { + 'reporter': 'spec', + 'reporter-options': '' + } +}); + +if (args.help) { + console.log(`Usage: node ${process.argv[1]} [options] + +Options: +--grep, -g, -f only run tests matching +--run only run tests from +--runGlob, --glob, --runGrep only run tests matching +--build run with build output (out-build) +--coverage generate coverage report +--per-test-coverage generate a per-test V8 coverage report, only valid with the full-json-stream reporter +--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: "") +--waitServer port to connect to and wait before running tests +--timeout timeout for tests +--crash-reporter-directory crash reporter directory +--tfs TFS server URL +--help, -h show the help`); + process.exit(0); +} + +let crashReporterDirectory = args['crash-reporter-directory']; +if (crashReporterDirectory) { + crashReporterDirectory = path.normalize(crashReporterDirectory); + + if (!path.isAbsolute(crashReporterDirectory)) { + console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`); + app.exit(1); + } + + if (!existsSync(crashReporterDirectory)) { + try { + mkdirSync(crashReporterDirectory); + } catch (error) { + console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`); + app.exit(1); + } + } + + // Crashes are stored in the crashDumps directory by default, so we + // need to change that directory to the provided one + console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`); + app.setPath('crashDumps', crashReporterDirectory); + + crashReporter.start({ + companyName: 'Microsoft', + productName: process.env['VSCODE_DEV'] ? `${product.nameShort} Dev` : product.nameShort, + uploadToServer: false, + compress: true + }); +} + +if (!args.dev) { + app.setPath('userData', path.join(tmpdir(), `vscode-tests-${Date.now()}`)); +} + +function deserializeSuite(suite) { + return { + root: suite.root, + suites: suite.suites, + tests: suite.tests, + title: suite.title, + titlePath: () => suite.titlePath, + fullTitle: () => suite.fullTitle, + timeout: () => suite.timeout, + retries: () => suite.retries, + slow: () => suite.slow, + bail: () => suite.bail + }; +} + +function deserializeRunnable(runnable) { + return { + title: runnable.title, + titlePath: () => runnable.titlePath, + fullTitle: () => runnable.fullTitle, + async: runnable.async, + slow: () => runnable.slow, + speed: runnable.speed, + duration: runnable.duration, + currentRetry: () => runnable.currentRetry + }; +} + +function deserializeError(err) { + const inspect = err.inspect; + err.inspect = () => inspect; + // Unfortunately, mocha rewrites and formats err.actual/err.expected. + // This formatting is hard to reverse, so err.*JSON includes the unformatted value. + if (err.actual) { + err.actual = JSON.parse(err.actual).value; + err.actualJSON = err.actual; + } + if (err.expected) { + err.expected = JSON.parse(err.expected).value; + err.expectedJSON = err.expected; + } + return err; +} + +class IPCRunner extends events.EventEmitter { + + constructor(win) { + super(); + + this.didFail = false; + this.didEnd = false; + + ipcMain.on('start', () => this.emit('start')); + ipcMain.on('end', () => { + this.didEnd = true; + this.emit('end'); + }); + ipcMain.on('suite', (e, suite) => this.emit('suite', deserializeSuite(suite))); + ipcMain.on('suite end', (e, suite) => this.emit('suite end', deserializeSuite(suite))); + ipcMain.on('test', (e, test) => this.emit('test', deserializeRunnable(test))); + ipcMain.on('test end', (e, test) => this.emit('test end', deserializeRunnable(test))); + ipcMain.on('hook', (e, hook) => this.emit('hook', deserializeRunnable(hook))); + ipcMain.on('hook end', (e, hook) => this.emit('hook end', deserializeRunnable(hook))); + ipcMain.on('pass', (e, test) => this.emit('pass', deserializeRunnable(test))); + ipcMain.on('fail', (e, test, err) => { + this.didFail = true; + this.emit('fail', deserializeRunnable(test), deserializeError(err)); + }); + ipcMain.on('pending', (e, test) => this.emit('pending', deserializeRunnable(test))); + + ipcMain.handle('startCoverage', async () => { + win.webContents.debugger.attach(); + await win.webContents.debugger.sendCommand('Debugger.enable'); + await win.webContents.debugger.sendCommand('Profiler.enable'); + await win.webContents.debugger.sendCommand('Profiler.startPreciseCoverage', { + detailed: true, + allowTriggeredUpdates: false, + }); + }); + + const coverageScriptsReported = new Set(); + ipcMain.handle('snapshotCoverage', async (_, test) => { + const coverage = await win.webContents.debugger.sendCommand('Profiler.takePreciseCoverage'); + await Promise.all(coverage.result.map(async (r) => { + if (!coverageScriptsReported.has(r.scriptId)) { + coverageScriptsReported.add(r.scriptId); + const src = await win.webContents.debugger.sendCommand('Debugger.getScriptSource', { scriptId: r.scriptId }); + r.source = src.scriptSource; + } + })); + + if (!test) { + this.emit('coverage init', coverage); + } else { + this.emit('coverage increment', test, coverage); + } + }); + } +} + +app.on('ready', () => { + + // needed when loading resources from the renderer, e.g xterm.js or the encoding lib + session.defaultSession.protocol.registerFileProtocol('vscode-file', (request, callback) => { + const path = new URL(request.url).pathname; + callback({ path }); + }); + + ipcMain.on('error', (_, err) => { + if (!args.dev) { + console.error(err); + app.exit(1); + } + }); + + // We need to provide a basic `ISandboxConfiguration` + // for our preload script to function properly because + // some of our types depend on it (e.g. product.ts). + ipcMain.handle('vscode:test-vscode-window-config', async () => { + return { + product: { + version: '1.x.y', + nameShort: 'Code - OSS Dev', + nameLong: 'Code - OSS Dev', + applicationName: 'code-oss', + dataFolderName: '.vscode-oss', + urlProtocol: 'code-oss', + } + }; + }); + + // No-op since invoke the IPC as part of IIFE in the preload. + ipcMain.handle('vscode:fetchShellEnv', event => { }); + + const win = new BrowserWindow({ + height: 600, + width: 800, + show: false, + webPreferences: { + preload: path.join(__dirname, '..', '..', '..', 'src', 'vs', 'base', 'parts', 'sandbox', 'electron-sandbox', 'preload.js'), // ensure similar environment as VSCode as tests may depend on this + additionalArguments: [`--vscode-window-config=vscode:test-vscode-window-config`], + nodeIntegration: true, + contextIsolation: false, + enableWebSQL: false, + spellcheck: false + } + }); + + win.webContents.on('did-finish-load', () => { + if (args.dev) { + win.show(); + win.webContents.openDevTools(); + } + + if (args.waitServer) { + waitForServer(Number(args.waitServer)).then(sendRun); + } else { + sendRun(); + } + }); + + async function waitForServer(port) { + let timeout; + let socket; + + return new Promise(resolve => { + socket = net.connect(port, '127.0.0.1'); + socket.on('error', e => { + console.error('error connecting to waitServer', e); + resolve(undefined); + }); + + socket.on('close', () => { + resolve(undefined); + }); + + timeout = setTimeout(() => { + console.error('timed out waiting for before starting tests debugger'); + resolve(undefined); + }, 15000); + }).finally(() => { + if (socket) { + socket.end(); + } + clearTimeout(timeout); + }); + } + + function sendRun() { + win.webContents.send('run', args); + } + + const target = url.pathToFileURL(path.join(__dirname, 'renderer.esm.html')); + target.searchParams.set('argv', JSON.stringify(args)); + win.loadURL(target.href); + + const runner = new IPCRunner(win); + createStatsCollector(runner); + + // Handle renderer crashes, #117068 + win.webContents.on('render-process-gone', (evt, details) => { + if (!runner.didEnd) { + console.error(`Renderer process crashed with: ${JSON.stringify(details)}`); + app.exit(1); + } + }); + + const reporters = []; + + if (args.tfs) { + reporters.push( + new mocha.reporters.Spec(runner), + new MochaJUnitReporter(runner, { + reporterOptions: { + testsuitesTitle: `${args.tfs} ${process.platform}`, + mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${args.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined + } + }), + ); + } else { + // mocha patches symbols to use windows escape codes, but it seems like + // Electron mangles these in its output. + if (process.platform === 'win32') { + Object.assign(importMochaReporter('base').symbols, { + ok: '+', + err: 'X', + dot: '.', + }); + } + + reporters.push(applyReporter(runner, args)); + } + + if (!args.dev) { + ipcMain.on('all done', async () => { + await Promise.all(reporters.map(r => r.drain?.())); + app.exit(runner.didFail ? 1 : 0); + }); + } +}); diff --git a/test/unit/electron/renderer.esm.html b/test/unit/electron/renderer.esm.html new file mode 100644 index 00000000000..8f6866efca5 --- /dev/null +++ b/test/unit/electron/renderer.esm.html @@ -0,0 +1,136 @@ + + + + + VSCode Tests + + + + + +
+ + + + + + + + diff --git a/test/unit/electron/renderer.esm.js b/test/unit/electron/renderer.esm.js new file mode 100644 index 00000000000..27d65249ac8 --- /dev/null +++ b/test/unit/electron/renderer.esm.js @@ -0,0 +1,366 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// @ts-check + +/*eslint-env mocha*/ + +const fs = require('fs'); + +(function () { + const originals = {}; + let logging = false; + let withStacks = false; + + globalThis.beginLoggingFS = (_withStacks) => { + logging = true; + withStacks = _withStacks || false; + }; + globalThis.endLoggingFS = () => { + logging = false; + withStacks = false; + }; + + function createSpy(element, cnt) { + return function (...args) { + if (logging) { + console.log(`calling ${element}: ` + args.slice(0, cnt).join(',') + (withStacks ? (`\n` + new Error().stack.split('\n').slice(2).join('\n')) : '')); + } + return originals[element].call(this, ...args); + }; + } + + function intercept(element, cnt) { + originals[element] = fs[element]; + fs[element] = createSpy(element, cnt); + } + + [ + ['realpathSync', 1], + ['readFileSync', 1], + ['openSync', 3], + ['readSync', 1], + ['closeSync', 1], + ['readFile', 2], + ['mkdir', 1], + ['lstat', 1], + ['stat', 1], + ['watch', 1], + ['readdir', 1], + ['access', 2], + ['open', 2], + ['write', 1], + ['fdatasync', 1], + ['close', 1], + ['read', 1], + ['unlink', 1], + ['rmdir', 1], + ].forEach((element) => { + intercept(element[0], element[1]); + }); +})(); + +const { ipcRenderer } = require('electron'); +const assert = require('assert'); +const path = require('path'); +const glob = require('glob'); +const util = require('util'); +const coverage = require('../coverage'); +const { pathToFileURL } = require('url'); + +// Disabled custom inspect. See #38847 +if (util.inspect && util.inspect['defaultOptions']) { + util.inspect['defaultOptions'].customInspect = false; +} + +// VSCODE_GLOBALS: node_modules +globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) }); + +// VSCODE_GLOBALS: package/product.json +globalThis._VSCODE_PRODUCT_JSON = require('../../../product.json'); +globalThis._VSCODE_PACKAGE_JSON = require('../../../package.json'); + +// Test file operations that are common across platforms. Used for test infra, namely snapshot tests +Object.assign(globalThis, { + __readFileInTests: path => fs.promises.readFile(path, 'utf-8'), + __writeFileInTests: (path, contents) => fs.promises.writeFile(path, contents), + __readDirInTests: path => fs.promises.readdir(path), + __unlinkInTests: path => fs.promises.unlink(path), + __mkdirPInTests: path => fs.promises.mkdir(path, { recursive: true }), +}); + +function initNls(opts) { + if (opts.build) { + // when running from `out-build`, ensure to load the default + // messages file, because all `nls.localize` calls have their + // english values removed and replaced by an index. + // VSCODE_GLOBALS: NLS + globalThis._VSCODE_NLS_MESSAGES = require(`../../../out-build/nls.messages.json`); + } +} +const _tests_glob = '**/test/**/*.test.js'; +let loader; +let _out; +const _loaderErrors = []; + +function initLoader(opts) { + // debugger; + const outdir = opts.build ? 'out-build' : 'out'; + _out = path.join(__dirname, `../../../${outdir}`); + + const baseUrl = pathToFileURL(path.join(__dirname, `../../../${outdir}/`)); + globalThis._VSCODE_FILE_ROOT = baseUrl.href; + + // set loader + /** + * @param {string[]} modules + * @param {(...args:any[]) => void} callback + */ + function esmRequire(modules, callback, errorback) { + const tasks = modules.map(mod => { + + const url = new URL(`./${mod}.js`, baseUrl).href; + return import(url).catch(err => { + console.log(mod, url); + console.log(err); + _loaderErrors.push(err); + throw err; + }); + }); + + Promise.all(tasks).then(modules => callback(...modules)).catch(errorback); + } + + loader = { require: esmRequire }; +} + +function createCoverageReport(opts) { + if (opts.coverage) { + return coverage.createReport(opts.run || opts.runGlob); + } + return Promise.resolve(undefined); +} + +function loadWorkbenchTestingUtilsModule() { + return new Promise((resolve, reject) => { + loader.require(['vs/workbench/test/common/utils'], resolve, reject); + }); +} + +async function loadModules(modules) { + for (const file of modules) { + mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, globalThis, file, mocha); + const m = await new Promise((resolve, reject) => loader.require([file], resolve, reject)); + mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_REQUIRE, m, file, mocha); + mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, globalThis, file, mocha); + } +} + +function loadTestModules(opts) { + + if (opts.run) { + const files = Array.isArray(opts.run) ? opts.run : [opts.run]; + const modules = files.map(file => { + file = file.replace(/^src/, 'out'); + file = file.replace(/\.ts$/, '.js'); + return path.relative(_out, file).replace(/\.js$/, ''); + }); + return loadModules(modules); + } + + const pattern = opts.runGlob || _tests_glob; + + return new Promise((resolve, reject) => { + glob(pattern, { cwd: _out }, (err, files) => { + if (err) { + reject(err); + return; + } + const modules = files.map(file => file.replace(/\.js$/, '')); + resolve(modules); + }); + }).then(loadModules); +} + +function loadTests(opts) { + + const _unexpectedErrors = []; + + // collect unexpected errors + loader.require(['vs/base/common/errors'], function (errors) { + errors.setUnexpectedErrorHandler(function (err) { + let stack = (err ? err.stack : null); + if (!stack) { + stack = new Error().stack; + } + + _unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack); + }); + }); + + return loadWorkbenchTestingUtilsModule().then((workbenchTestingModule) => { + const assertCleanState = workbenchTestingModule.assertCleanState; + + suite('Tests are using suiteSetup and setup correctly', () => { + test('assertCleanState - check that registries are clean at the start of test running', () => { + assertCleanState(); + }); + }); + + return loadTestModules(opts).then(() => { + suite('Unexpected Errors & Loader Errors', function () { + test('should not have unexpected errors', function () { + const errors = _unexpectedErrors.concat(_loaderErrors); + if (errors.length) { + errors.forEach(function (stack) { + console.error(''); + console.error(stack); + }); + assert.ok(false, errors.join()); + } + }); + + test('assertCleanState - check that registries are clean and objects are disposed at the end of test running', () => { + assertCleanState(); + }); + }); + }); + }); +} + +function serializeSuite(suite) { + return { + root: suite.root, + suites: suite.suites.map(serializeSuite), + tests: suite.tests.map(serializeRunnable), + title: suite.title, + fullTitle: suite.fullTitle(), + titlePath: suite.titlePath(), + timeout: suite.timeout(), + retries: suite.retries(), + slow: suite.slow(), + bail: suite.bail() + }; +} + +function serializeRunnable(runnable) { + return { + title: runnable.title, + fullTitle: runnable.fullTitle(), + titlePath: runnable.titlePath(), + async: runnable.async, + slow: runnable.slow(), + speed: runnable.speed, + duration: runnable.duration + }; +} + +function serializeError(err) { + return { + message: err.message, + stack: err.stack, + snapshotPath: err.snapshotPath, + actual: safeStringify({ value: err.actual }), + expected: safeStringify({ value: err.expected }), + uncaught: err.uncaught, + showDiff: err.showDiff, + inspect: typeof err.inspect === 'function' ? err.inspect() : '' + }; +} + +function safeStringify(obj) { + const seen = new Set(); + return JSON.stringify(obj, (key, value) => { + if (value === undefined) { + return '[undefined]'; + } + + if (isObject(value) || Array.isArray(value)) { + if (seen.has(value)) { + return '[Circular]'; + } else { + seen.add(value); + } + } + return value; + }); +} + +function isObject(obj) { + // The method can't do a type cast since there are type (like strings) which + // are subclasses of any put not positvely matched by the function. Hence type + // narrowing results in wrong results. + return typeof obj === 'object' + && obj !== null + && !Array.isArray(obj) + && !(obj instanceof RegExp) + && !(obj instanceof Date); +} + +class IPCReporter { + + constructor(runner) { + runner.on('start', () => ipcRenderer.send('start')); + runner.on('end', () => ipcRenderer.send('end')); + runner.on('suite', suite => ipcRenderer.send('suite', serializeSuite(suite))); + runner.on('suite end', suite => ipcRenderer.send('suite end', serializeSuite(suite))); + runner.on('test', test => ipcRenderer.send('test', serializeRunnable(test))); + runner.on('test end', test => ipcRenderer.send('test end', serializeRunnable(test))); + runner.on('hook', hook => ipcRenderer.send('hook', serializeRunnable(hook))); + runner.on('hook end', hook => ipcRenderer.send('hook end', serializeRunnable(hook))); + runner.on('pass', test => ipcRenderer.send('pass', serializeRunnable(test))); + runner.on('fail', (test, err) => ipcRenderer.send('fail', serializeRunnable(test), serializeError(err))); + runner.on('pending', test => ipcRenderer.send('pending', serializeRunnable(test))); + } +} + +function runTests(opts) { + // this *must* come before loadTests, or it doesn't work. + if (opts.timeout !== undefined) { + mocha.timeout(opts.timeout); + } + + return loadTests(opts).then(() => { + + if (opts.grep) { + mocha.grep(opts.grep); + } + + if (!opts.dev) { + mocha.reporter(IPCReporter); + } + + const runner = mocha.run(() => { + createCoverageReport(opts).then(() => { + ipcRenderer.send('all done'); + }); + }); + + if (opts.dev) { + runner.on('fail', (test, err) => { + + console.error(test.fullTitle()); + console.error(err.stack); + }); + } + }); +} + +ipcRenderer.on('run', async (_e, opts) => { + initNls(opts); + initLoader(opts); + + await Promise.resolve(globalThis._VSCODE_TEST_INIT); + + try { + await runTests(opts); + } catch (err) { + if (typeof err !== 'string') { + err = JSON.stringify(err); + } + console.error(err); + ipcRenderer.send('error', err); + } +}); diff --git a/test/unit/node/index.mjs b/test/unit/node/index.mjs new file mode 100644 index 00000000000..0193be6843b --- /dev/null +++ b/test/unit/node/index.mjs @@ -0,0 +1,249 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check +'use strict'; + +process.env.MOCHA_COLORS = '1'; // Force colors (note that this must come before any mocha imports) + +import * as assert from 'assert'; +import Mocha from 'mocha'; +import * as path from 'path'; +import * as fs from 'fs'; +import glob from 'glob'; +import minimatch from 'minimatch'; +// const coverage = require('../coverage'); +import minimist from 'minimist'; +// const { takeSnapshotAndCountClasses } = require('../analyzeSnapshot'); +import * as module from 'module'; +import { fileURLToPath, pathToFileURL } from 'url'; + +/** + * @type {{ build: boolean; run: string; runGlob: string; coverage: boolean; help: boolean; coverageFormats: string | string[]; coveragePath: string; }} + */ +const args = minimist(process.argv.slice(2), { + boolean: ['build', 'coverage', 'help'], + string: ['run', 'coveragePath', 'coverageFormats'], + alias: { + h: 'help' + }, + default: { + build: false, + coverage: false, + help: false + }, + description: { + build: 'Run from out-build', + run: 'Run a single file', + coverage: 'Generate a coverage report', + coveragePath: 'Path to coverage report to generate', + coverageFormats: 'Coverage formats to generate', + help: 'Show help' + } +}); + +if (args.help) { + console.log(`Usage: node test/unit/node/index [options] + +Options: +--build Run from out-build +--run Run a single file +--coverage Generate a coverage report +--help Show help`); + process.exit(0); +} + +const TEST_GLOB = '**/test/**/*.test.js'; + +const excludeGlobs = [ + '**/{browser,electron-sandbox,electron-main}/**/*.test.js', + '**/vs/platform/environment/test/node/nativeModules.test.js', // native modules are compiled against Electron and this test would fail with node.js + '**/vs/base/parts/storage/test/node/storage.test.js', // same as above, due to direct dependency to sqlite native module + '**/vs/workbench/contrib/testing/test/**' // flaky (https://github.com/microsoft/vscode/issues/137853) +]; + +const REPO_ROOT = fileURLToPath(new URL('../../../', import.meta.url)); +const out = args.build ? 'out-build' : 'out'; +const src = path.join(REPO_ROOT, out); +const baseUrl = pathToFileURL(src); + +//@ts-ignore +const majorRequiredNodeVersion = `v${/^target\s+"([^"]+)"$/m.exec(fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8'))[1]}`.substring(0, 3); +const currentMajorNodeVersion = process.version.substring(0, 3); +if (majorRequiredNodeVersion !== currentMajorNodeVersion) { + console.error(`node.js unit tests require a major node.js version of ${majorRequiredNodeVersion} (your version is: ${currentMajorNodeVersion})`); + process.exit(1); +} + +function main() { + + // VSCODE_GLOBALS: node_modules + const _require = module.createRequire(import.meta.url); + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => _require(String(mod)) }); + + // VSCODE_GLOBALS: package/product.json + globalThis._VSCODE_PRODUCT_JSON = _require(`${REPO_ROOT}/product.json`); + globalThis._VSCODE_PACKAGE_JSON = _require(`${REPO_ROOT}/package.json`); + + // VSCODE_GLOBALS: file root + globalThis._VSCODE_FILE_ROOT = baseUrl.href; + + if (args.build) { + // when running from `out-build`, ensure to load the default + // messages file, because all `nls.localize` calls have their + // english values removed and replaced by an index. + globalThis._VSCODE_NLS_MESSAGES = _require(`${REPO_ROOT}/${out}/nls.messages.json`); + } + + // Test file operations that are common across platforms. Used for test infra, namely snapshot tests + Object.assign(globalThis, { + // __analyzeSnapshotInTests: takeSnapshotAndCountClasses, + __readFileInTests: (/** @type {string} */ path) => fs.promises.readFile(path, 'utf-8'), + __writeFileInTests: (/** @type {string} */ path, /** @type {BufferEncoding} */ contents) => fs.promises.writeFile(path, contents), + __readDirInTests: (/** @type {string} */ path) => fs.promises.readdir(path), + __unlinkInTests: (/** @type {string} */ path) => fs.promises.unlink(path), + __mkdirPInTests: (/** @type {string} */ path) => fs.promises.mkdir(path, { recursive: true }), + }); + + process.on('uncaughtException', function (e) { + console.error(e.stack || e); + }); + + /** + * @param modules + * @param onLoad + * @param onError + */ + const loader = function (modules, onLoad, onError) { + + modules = modules.filter(mod => { + if (mod.endsWith('css.build.test')) { + // AMD ONLY, ignore for ESM + return false; + } + return true; + }); + + const loads = modules.map(mod => import(`${baseUrl}/${mod}.js`).catch(err => { + console.error(`FAILED to load ${mod} as ${baseUrl}/${mod}.js`); + throw err; + })); + Promise.all(loads).then(onLoad, onError); + }; + + + let didErr = false; + const write = process.stderr.write; + process.stderr.write = function (...args) { + didErr = didErr || !!args[0]; + return write.apply(process.stderr, args); + }; + + + const runner = new Mocha({ + ui: 'tdd' + }); + + /** + * @param modules + */ + async function loadModules(modules) { + for (const file of modules) { + runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, globalThis, file, runner); + const m = await new Promise((resolve, reject) => loader([file], resolve, reject)); + runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_REQUIRE, m, file, runner); + runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, globalThis, file, runner); + } + } + + /** @type { null|((callback:(err:any)=>void)=>void) } */ + let loadFunc = null; + + if (args.runGlob) { + loadFunc = (cb) => { + const doRun = /** @param tests */(tests) => { + const modulesToLoad = tests.map(test => { + if (path.isAbsolute(test)) { + test = path.relative(src, path.resolve(test)); + } + + return test.replace(/(\.js)|(\.d\.ts)|(\.js\.map)$/, ''); + }); + loadModules(modulesToLoad).then(() => cb(null), cb); + }; + + glob(args.runGlob, { cwd: src }, function (err, files) { doRun(files); }); + }; + } else if (args.run) { + const tests = (typeof args.run === 'string') ? [args.run] : args.run; + const modulesToLoad = tests.map(function (test) { + test = test.replace(/^src/, 'out'); + test = test.replace(/\.ts$/, '.js'); + return path.relative(src, path.resolve(test)).replace(/(\.js)|(\.js\.map)$/, '').replace(/\\/g, '/'); + }); + loadFunc = (cb) => { + loadModules(modulesToLoad).then(() => cb(null), cb); + }; + } else { + loadFunc = (cb) => { + glob(TEST_GLOB, { cwd: src }, function (err, files) { + /** @type {string[]} */ + const modules = []; + for (const file of files) { + if (!excludeGlobs.some(excludeGlob => minimatch(file, excludeGlob))) { + modules.push(file.replace(/\.js$/, '')); + } + } + loadModules(modules).then(() => cb(null), cb); + }); + }; + } + + loadFunc(function (err) { + if (err) { + console.error(err); + return process.exit(1); + } + + process.stderr.write = write; + + if (!args.run && !args.runGlob) { + // set up last test + Mocha.suite('Loader', function () { + test('should not explode while loading', function () { + assert.ok(!didErr, `should not explode while loading: ${didErr}`); + }); + }); + } + + // report failing test for every unexpected error during any of the tests + const unexpectedErrors = []; + Mocha.suite('Errors', function () { + test('should not have unexpected errors in tests', function () { + if (unexpectedErrors.length) { + unexpectedErrors.forEach(function (stack) { + console.error(''); + console.error(stack); + }); + + assert.ok(false); + } + }); + }); + + // replace the default unexpected error handler to be useful during tests + import(`${baseUrl}/vs/base/common/errors.js`).then(errors => { + errors.setUnexpectedErrorHandler(function (err) { + const stack = (err && err.stack) || (new Error().stack); + unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack); + }); + + // fire up mocha + runner.run(failures => process.exit(failures ? 1 : 0)); + }); + }); +} + +main(); diff --git a/yarn.lock b/yarn.lock index f91cd5dc3e6..d2540532541 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9581,7 +9581,7 @@ streamx@^2.18.0: optionalDependencies: bare-events "^2.2.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9616,15 +9616,6 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -9687,7 +9678,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9715,13 +9706,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -10686,10 +10670,10 @@ vscode-regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-textmate@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" - integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== +vscode-textmate@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.1.0.tgz#656a6aa163a9578397ba810733952bedb2b47202" + integrity sha512-lxKSVp2DkFOx9RDAvpiYUrB9/KT1fAfi1aE8CBGstP8N7rLF+Seifj8kDA198X0mYj1CjQUC+81+nQf8CO0nVA== vscode-uri@^3.0.8: version "3.0.8" @@ -10891,7 +10875,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -10926,15 +10910,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"