diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78a4f04991..7a0636f7d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,46 +11,49 @@ on: - release/* jobs: - # linux: - # runs-on: ubuntu-latest - # env: - # CHILD_CONCURRENCY: "1" - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # steps: - # - uses: actions/checkout@v1 - # # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - # - run: | - # sudo apt-get update - # sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libkrb5-dev # {{SQL CARBON EDIT}} add kerberos dep - # sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb - # sudo chmod +x /etc/init.d/xvfb - # sudo update-rc.d xvfb defaults - # sudo service xvfb start - # name: Setup Build Environment - # - uses: actions/setup-node@v1 - # with: - # node-version: 10 - # # TODO: cache node modules - # - run: yarn --frozen-lockfile - # name: Install Dependencies - # - run: yarn electron x64 - # name: Download Electron - # - run: yarn gulp hygiene - # name: Run Hygiene Checks - # - run: yarn strict-vscode # {{SQL CARBON EDIT}} add step - # name: Run Strict Compile Options - # # - run: yarn monaco-compile-check {{SQL CARBON EDIT}} remove step - # # name: Run Monaco Editor Checks - # - run: yarn valid-layers-check - # name: Run Valid Layers Checks - # - run: yarn compile - # name: Compile Sources - # # - run: yarn download-builtin-extensions {{SQL CARBON EDIT}} remove step - # # name: Download Built-in Extensions - # - run: DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" - # name: Run Unit Tests - # # - run: DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" {{SQL CARBON EDIT}} remove step - # # name: Run Integration Tests + linux: + runs-on: ubuntu-latest + env: + CHILD_CONCURRENCY: "1" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v1 + # TODO: rename azure-pipelines/linux/xvfb.init to github-actions + - run: | + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libkrb5-dev # {{SQL CARBON EDIT}} add kerberos dep + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + name: Setup Build Environment + - uses: actions/setup-node@v1 + with: + node-version: 10 + # TODO: cache node modules + - run: yarn --frozen-lockfile + name: Install Dependencies + - run: yarn electron x64 + name: Download Electron + - run: yarn gulp hygiene + name: Run Hygiene Checks + - run: yarn strict-vscode # {{SQL CARBON EDIT}} add step + name: Run Strict Compile Options + # - run: yarn monaco-compile-check {{SQL CARBON EDIT}} remove step + # name: Run Monaco Editor Checks + - run: yarn valid-layers-check + name: Run Valid Layers Checks + - run: yarn compile + name: Compile Sources + # - run: yarn download-builtin-extensions {{SQL CARBON EDIT}} remove step + # name: Download Built-in Extensions + - run: DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" + name: Run Unit Tests (Electron) + # Fails with cryptic error (e.g. https://github.com/microsoft/vscode/pull/90292/checks?check_run_id=433681926#step:13:9) + # - run: DISPLAY=:10 yarn test-browser --browser chromium + # name: Run Unit Tests (Browser) + # - run: DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" {{SQL CARBON EDIT}} remove step + # name: Run Integration Tests (Electron) windows: runs-on: windows-2016 @@ -82,9 +85,11 @@ jobs: # - run: yarn download-builtin-extensions {{SQL CARBON EDIT}} remove step # name: Download Built-in Extensions - run: .\scripts\test.bat --tfs "Unit Tests" - name: Run Unit Tests + name: Run Unit Tests (Electron) + - run: yarn test-browser --browser chromium + name: Run Unit Tests (Browser) # - run: .\scripts\test-integration.bat --tfs "Integration Tests" {{SQL CARBON EDIT}} remove step - # name: Run Integration Tests + # name: Run Integration Tests (Electron) darwin: runs-on: macos-latest @@ -113,6 +118,8 @@ jobs: # - run: yarn download-builtin-extensions {{SQL CARBON EDIT}} remove step # name: Download Built-in Extensions - run: ./scripts/test.sh --tfs "Unit Tests" - name: Run Unit Tests + name: Run Unit Tests (Electron) + - run: yarn test-browser --browser chromium --browser webkit + name: Run Unit Tests (Browser) # - run: ./scripts/test-integration.sh --tfs "Integration Tests" {{SQL CARBON EDIT}} remove step - # name: Run Integration Tests + # name: Run Integration Tests (Electron) diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index afdd39fcaf..0849c5a685 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -43,13 +43,13 @@ steps: # displayName: Download Built-in Extensions - script: | ./scripts/test.sh --tfs "Unit Tests" - displayName: Run Unit Tests + displayName: Run Unit Tests (Electron) - script: | yarn test-browser --browser chromium --browser webkit - displayName: Run Unit Tests (Browsers) + displayName: Run Unit Tests (Browser) # - script: | {{SQL CARBON EDIT}} remove step # ./scripts/test-integration.sh --tfs "Integration Tests" -# displayName: Run Integration Tests +# displayName: Run Integration Tests (Electron) - task: PublishTestResults@2 displayName: Publish Tests Results inputs: diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 55f55e0ead..bb75d8570d 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -44,13 +44,6 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" - - security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - security default-keychain -s $(agent.tempdirectory)/buildagent.keychain - security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 - security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain displayName: Prepare tooling - script: | @@ -103,9 +96,13 @@ steps: - script: | set -e ./scripts/test.sh --build --tfs "Unit Tests" - # APP_NAME="`ls $(agent.builddirectory)/VSCode-darwin | head -n 1`" - # yarn smoketest -- --build "$(agent.builddirectory)/VSCode-darwin/$APP_NAME" - displayName: Run unit tests + displayName: Run unit tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e + yarn test-browser --build --browser chromium --browser webkit + displayName: Run unit tests (Browser) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | @@ -118,22 +115,32 @@ steps: INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \ ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests + displayName: Run integration tests (Electron) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e - cd test/smoke - yarn compile - cd - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ - yarn smoketest --web --headless - continueOnError: true - displayName: Run web smoke tests + ./resources/server/test/test-web-integration.sh --browser webkit + displayName: Run integration tests (Browser) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ + yarn smoketest --web --headless --browser webkit + continueOnError: true + displayName: Run smoke tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e + security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + security default-keychain -s $(agent.tempdirectory)/buildagent.keychain + security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 + security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain codesign -s 99FM488X57 --deep --force --options runtime --entitlements build/azure-pipelines/darwin/entitlements.plist $(agent.builddirectory)/VSCode-darwin/*.app displayName: Set Hardened Entitlements diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index e5349d097d..2d0209cf0b 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -51,13 +51,13 @@ steps: # displayName: Download Built-in Extensions - script: | DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" - displayName: Run Unit Tests + displayName: Run Unit Tests (Electron) - script: | DISPLAY=:10 yarn test-browser --browser chromium displayName: Run Unit Tests (Browser) # - script: | {{SQL CARBON EDIT}} remove step # DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" -# displayName: Run Integration Tests +# displayName: Run Integration Tests (Electron) - task: PublishTestResults@2 displayName: Publish Tests Results inputs: diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 573d7c7d4c..7cd8e73039 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -101,7 +101,13 @@ steps: - script: | set -e DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests + displayName: Run unit tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e + DISPLAY=:10 yarn test-browser --build --browser chromium + displayName: Run unit tests (Browser) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | @@ -114,8 +120,23 @@ steps: INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-x64" \ DISPLAY=:10 ./scripts/test-integration.sh --build --tfs "Integration Tests" - # yarn smoketest -- --build "$(agent.builddirectory)/VSCode-linux-x64" - displayName: Run integration tests + displayName: Run integration tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +# Fails due to weird error: Protocol error (Target.getBrowserContexts): Target closed. +# - script: | +# set -e +# VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-x64" \ +# DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium +# displayName: Run integration tests (Browser) +# condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-x64" \ + yarn smoketest --web --headless --browser firefox + continueOnError: true + displayName: Run smoke tests (Firefox) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index fc80b0bef1..cde9952139 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -48,13 +48,13 @@ steps: # displayName: Download Built-in Extensions - powershell: | .\scripts\test.bat --tfs "Unit Tests" - displayName: Run Unit Tests + displayName: Run Unit Tests (Electron) - powershell: | - yarn test-browser --browser chromium --browser webkit + yarn test-browser --browser chromium displayName: Run Unit Tests (Browser) # - powershell: | {{SQL CARBON EDIT}} remove step # .\scripts\test-integration.bat --tfs "Integration Tests" -# displayName: Run Integration Tests +# displayName: Run Integration Tests (Electron) - task: PublishTestResults@2 displayName: Publish Tests Results inputs: diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 4c9336c6c1..796259ab98 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -109,7 +109,14 @@ steps: $ErrorActionPreference = "Stop" exec { yarn electron $(VSCODE_ARCH) } exec { .\scripts\test.bat --build --tfs "Unit Tests" } - displayName: Run unit tests + displayName: Run unit tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-browser --build --browser chromium } + displayName: Run unit tests (Browser) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - powershell: | @@ -122,7 +129,22 @@ steps: $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json $AppNameShort = $AppProductJson.nameShort exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } - displayName: Run integration tests + displayName: Run integration tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-web-integration.bat --browser chromium } + displayName: Run integration tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; yarn smoketest --web --headless --browser chromium } + continueOnError: true + displayName: Run smoke tests (Browser) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 19567d741b..d04684d7d7 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -333,7 +333,7 @@ function sanitizePath(path: string): string { return path.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`); } -const COMMIT_FORMAT = '%H\n%aN\n%aE\n%at\n%P\n%B'; +const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%P%n%B'; export class Git { @@ -801,8 +801,8 @@ export class Repository { } async log(options?: LogOptions): Promise { - const maxEntries = options && typeof options.maxEntries === 'number' && options.maxEntries > 0 ? options.maxEntries : 32; - const args = ['log', '-' + maxEntries, `--format:${COMMIT_FORMAT}`, '-z']; + const maxEntries = options?.maxEntries ?? 32; + const args = ['log', `-n${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z', '--']; const result = await this.run(args); if (result.exitCode) { @@ -815,7 +815,7 @@ export class Repository { async logFile(uri: Uri, options?: LogFileOptions): Promise { const maxEntries = options?.maxEntries ?? 32; - const args = ['log', `-${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z', '--', uri.fsPath]; + const args = ['log', `-n${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z', '--', uri.fsPath]; const result = await this.run(args); if (result.exitCode) { diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 8503fb393e..82c8f9fae3 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -333,7 +333,7 @@ "dependencies": { "highlight.js": "9.15.10", "markdown-it": "^10.0.0", - "markdown-it-front-matter": "^0.1.2", + "markdown-it-front-matter": "^0.2.1", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0" }, diff --git a/extensions/markdown-language-features/src/test/test-fixtures/marker.txt b/extensions/markdown-language-features/src/test/test-fixtures/marker.txt new file mode 100644 index 0000000000..a4c9ad4073 --- /dev/null +++ b/extensions/markdown-language-features/src/test/test-fixtures/marker.txt @@ -0,0 +1 @@ +DO NOT DELETE, USED BY INTEGRATION TESTS diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 8ec43a6d0e..5c7a22c74f 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -2821,10 +2821,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-it-front-matter@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.1.2.tgz#e50bf56e77e6a4f5ac4ffa894d4d45ccd9896b20" - integrity sha1-5Qv1bnfmpPWsT/qJTU1FzNmJayA= +markdown-it-front-matter@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.2.1.tgz#dca49a827bb3cebb0528452c1d87dff276eb28dc" + integrity sha512-ydUIqlKfDscRpRUTRcA3maeeUKn3Cl5EaKZSA+I/f0KOGCBurW7e+bbz59sxqkC3FA9Q2S2+t4mpkH9T0BCM6A== markdown-it@^10.0.0: version "10.0.0" diff --git a/package.json b/package.json index f6dfa1f2fa..04a3e61bc1 100644 --- a/package.json +++ b/package.json @@ -75,11 +75,11 @@ "vscode-minimist": "^1.2.2", "vscode-nsfw": "1.2.8", "vscode-proxy-agent": "^0.5.2", - "vscode-ripgrep": "^1.5.7", + "vscode-ripgrep": "^1.5.8", "vscode-sqlite3": "4.0.9", "vscode-textmate": "4.4.0", "xterm": "4.4.0", - "xterm-addon-search": "0.4.0", + "xterm-addon-search": "0.5.0", "xterm-addon-unicode11": "0.1.1", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.5.0", diff --git a/remote/package.json b/remote/package.json index 3481542d70..14b25d4666 100644 --- a/remote/package.json +++ b/remote/package.json @@ -18,10 +18,10 @@ "vscode-minimist": "^1.2.2", "vscode-nsfw": "1.2.8", "vscode-proxy-agent": "^0.5.2", - "vscode-ripgrep": "^1.5.7", + "vscode-ripgrep": "^1.5.8", "vscode-textmate": "4.4.0", "xterm": "4.4.0", - "xterm-addon-search": "0.4.0", + "xterm-addon-search": "0.5.0", "xterm-addon-unicode11": "0.1.1", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.5.0", diff --git a/remote/web/package.json b/remote/web/package.json index 70a55a9074..cdb1fd8db2 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -6,7 +6,7 @@ "semver-umd": "^5.5.5", "vscode-textmate": "4.4.0", "xterm": "4.4.0", - "xterm-addon-search": "0.4.0", + "xterm-addon-search": "0.5.0", "xterm-addon-unicode11": "0.1.1", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.5.0" diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index b4c1b7d0b6..0b1d4fb601 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -31,10 +31,10 @@ vscode-textmate@4.4.0: dependencies: oniguruma "^7.2.0" -xterm-addon-search@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0.tgz#a7beadb3caa7330eb31fb1f17d92de25537684a1" - integrity sha512-g07qb/Z4aSfrQ25e6Z6rz6KiExm2DvesQXkx+eA715VABBr5VM/9Jf0INoCiDSYy/nn7rpna+kXiGVJejIffKg== +xterm-addon-search@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.5.0.tgz#cd3a2f8056084c28e236d4e732da37682010bcc2" + integrity sha512-zLVqVTrg5w2nk9fRj3UuVKCPo/dmFe/cLf3EM9Is5Dm6cgOoXmeo9eq2KgD8A0gquAflTFTf0ya2NaFmShHwyg== xterm-addon-unicode11@0.1.1: version "0.1.1" diff --git a/remote/yarn.lock b/remote/yarn.lock index bc865255b6..3fa3b05348 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -389,10 +389,10 @@ vscode-proxy-agent@^0.5.2: https-proxy-agent "^2.2.3" socks-proxy-agent "^4.0.1" -vscode-ripgrep@^1.5.7: - version "1.5.7" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.7.tgz#acb6b548af488a4bca5d0f1bb5faf761343289ce" - integrity sha512-/Vsz/+k8kTvui0q3O74pif9FK0nKopgFTiGNVvxicZANxtSA8J8gUE9GQ/4dpi7D/2yI/YVORszwVskFbz46hQ== +vscode-ripgrep@^1.5.8: + version "1.5.8" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.8.tgz#32cb33da6d1a9ca8f5de8c2813ed5114fd55fc11" + integrity sha512-l6Pv/t1Jk63RU+kEkMO04XxnNRYdyzuesizj9AzFpcfrUxxpAjEJBK1qO9Mov30UUGZl7uDUBn+uCv9koaHPPA== vscode-textmate@4.4.0: version "4.4.0" @@ -413,10 +413,10 @@ vscode-windows-registry@1.0.2: resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a" integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA== -xterm-addon-search@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0.tgz#a7beadb3caa7330eb31fb1f17d92de25537684a1" - integrity sha512-g07qb/Z4aSfrQ25e6Z6rz6KiExm2DvesQXkx+eA715VABBr5VM/9Jf0INoCiDSYy/nn7rpna+kXiGVJejIffKg== +xterm-addon-search@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.5.0.tgz#cd3a2f8056084c28e236d4e732da37682010bcc2" + integrity sha512-zLVqVTrg5w2nk9fRj3UuVKCPo/dmFe/cLf3EM9Is5Dm6cgOoXmeo9eq2KgD8A0gquAflTFTf0ya2NaFmShHwyg== xterm-addon-unicode11@0.1.1: version "0.1.1" diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 20bbbb03b9..14c62def64 100755 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -39,17 +39,20 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: Tests in the extension host -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 --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% -if %errorlevel% neq 0 exit /b %errorlevel% +REM 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 --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +REM if %errorlevel% neq 0 exit /b %errorlevel% -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 --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% -if %errorlevel% neq 0 exit /b %errorlevel% +REM 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 --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +REM if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% -if %errorlevel% neq 0 exit /b %errorlevel% +REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +REM if %errorlevel% neq 0 exit /b %errorlevel% -:: call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% . -:: if %errorlevel% neq 0 exit /b %errorlevel% +REM call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\markdown-language-features\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% . +REM if %errorlevel% neq 0 exit /b %errorlevel% + +REM call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% . +REM if %errorlevel% neq 0 exit /b %errorlevel% call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\azurecore\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azurecore --extensionTestsPath=%~dp0\..\extensions\azurecore\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 691dcf1077..b74c5189a3 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -44,14 +44,12 @@ fi # Tests in the extension host # "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $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 --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR # "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $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 --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR # "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -mkdir -p $ROOT/extensions/emmet/test-fixtures -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -rm -rf $ROOT/extensions/emmet/test-fixtures +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR # Remote Integration Tests if [ -f ./resources/server/test/test-remote-integration.sh ]; then diff --git a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts index bd7c0a96f1..9dc0f761fb 100644 --- a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts @@ -35,7 +35,7 @@ import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/mod import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput'; import { find } from 'vs/base/common/arrays'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; diff --git a/src/sql/workbench/browser/modelComponents/editor.component.ts b/src/sql/workbench/browser/modelComponents/editor.component.ts index cb2491f037..91f4025304 100644 --- a/src/sql/workbench/browser/modelComponents/editor.component.ts +++ b/src/sql/workbench/browser/modelComponents/editor.component.ts @@ -19,7 +19,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase'; import { QueryTextEditor } from 'sql/workbench/browser/modelComponents/queryTextEditor'; import { ILogService } from 'vs/platform/log/common/log'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { SimpleProgressIndicator } from 'sql/workbench/services/progress/browser/simpleProgressIndicator'; diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts index d931398f6b..50b57bba6c 100644 --- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts +++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts @@ -21,7 +21,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; /** diff --git a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts index a4a919c23f..105477746c 100644 --- a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts +++ b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts @@ -33,7 +33,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; class TestParsedArgs implements ParsedArgs { diff --git a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts index c03c658ccb..716b72c530 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts @@ -29,7 +29,7 @@ import { import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { IFlexibleSash, HorizontalFlexibleSash } from 'sql/workbench/contrib/query/browser/flexibleSash'; import { EditDataResultsEditor } from 'sql/workbench/contrib/editData/browser/editDataResultsEditor'; import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput'; diff --git a/src/sql/workbench/contrib/editData/browser/editDataInput.ts b/src/sql/workbench/contrib/editData/browser/editDataInput.ts index 945f708e0f..12a30edb45 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataInput.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataInput.ts @@ -14,7 +14,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import Severity from 'vs/base/common/severity'; import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; /** * Input for the EditDataEditor. diff --git a/src/sql/workbench/contrib/editorReplacement/common/editorReplacerContribution.ts b/src/sql/workbench/contrib/editorReplacement/common/editorReplacerContribution.ts index 76c84ca921..e7b56c6d0e 100644 --- a/src/sql/workbench/contrib/editorReplacement/common/editorReplacerContribution.ts +++ b/src/sql/workbench/contrib/editorReplacement/common/editorReplacerContribution.ts @@ -15,7 +15,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as path from 'vs/base/common/path'; import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/services/languageAssociation/common/languageAssociation'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; const languageAssociationRegistry = Registry.as(LanguageAssociationExtensions.LanguageAssociations); diff --git a/src/sql/workbench/contrib/editorReplacement/test/common/editorReplacerContribution.test.ts b/src/sql/workbench/contrib/editorReplacement/test/common/editorReplacerContribution.test.ts index 15649fe368..823a655ee4 100644 --- a/src/sql/workbench/contrib/editorReplacement/test/common/editorReplacerContribution.test.ts +++ b/src/sql/workbench/contrib/editorReplacement/test/common/editorReplacerContribution.test.ts @@ -24,7 +24,7 @@ import { QueryEditorLanguageAssociation } from 'sql/workbench/contrib/query/comm import { workbenchInstantiationService } from 'sql/workbench/test/workbenchTestServices'; import { NotebookEditorInputAssociation } from 'sql/workbench/contrib/notebook/common/models/nodebookInputFactory'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput'; import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; import { NotebookServiceStub } from 'sql/workbench/contrib/notebook/test/stubs'; diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts index 5ab03f5a4c..c0c97ed5a8 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts @@ -30,8 +30,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { CollapseComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/collapse.component'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CellView } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { SimpleProgressIndicator } from 'sql/workbench/services/progress/browser/simpleProgressIndicator'; diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts index 05bd5b2100..65de4ab037 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts @@ -26,8 +26,8 @@ import { Deferred } from 'sql/base/common/promise'; import { NotebookTextFileModel } from 'sql/workbench/contrib/notebook/browser/models/notebookTextFileModel'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; diff --git a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts index 5b62c6f2e2..f7ec938118 100644 --- a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts +++ b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts @@ -10,7 +10,7 @@ import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput'; import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { ILanguageAssociation } from 'sql/workbench/services/languageAssociation/common/languageAssociation'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; diff --git a/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts b/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts index 9f2b54c858..3e421191fc 100644 --- a/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts +++ b/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts @@ -9,7 +9,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; export class UntitledNotebookInput extends NotebookInput { public static ID: string = 'workbench.editorinputs.untitledNotebookInput'; diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts index a3a213f6a6..10516a86fe 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts @@ -12,10 +12,10 @@ import { URI } from 'vs/base/common/uri'; import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput'; import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { NodeStub, NotebookServiceStub } from 'sql/workbench/contrib/notebook/test/stubs'; import { basenameOrAuthority } from 'vs/base/common/resources'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { INotebookService, IProviderInfo } from 'sql/workbench/services/notebook/browser/notebookService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; diff --git a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts index fb45615c2e..9b3bb61254 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts @@ -51,7 +51,7 @@ import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelect import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { find } from 'vs/base/common/arrays'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { attachTabbedPanelStyler } from 'sql/workbench/common/styler'; class BasicView implements IView { diff --git a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts index 31677b008c..6693c54447 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts @@ -19,7 +19,7 @@ import { EditorOptions } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; class ProfilerResourceCodeEditor extends CodeEditorWidget { diff --git a/src/sql/workbench/contrib/query/common/queryInputFactory.ts b/src/sql/workbench/contrib/query/common/queryInputFactory.ts index 3676024f99..8e85a2a817 100644 --- a/src/sql/workbench/contrib/query/common/queryInputFactory.ts +++ b/src/sql/workbench/contrib/query/common/queryInputFactory.ts @@ -11,7 +11,7 @@ import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput'; import { FileQueryEditorInput } from 'sql/workbench/contrib/query/common/fileQueryEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { ILanguageAssociation } from 'sql/workbench/services/languageAssociation/common/languageAssociation'; import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; import { getCurrentGlobalConnection } from 'sql/workbench/browser/taskUtilities'; diff --git a/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts b/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts index d46d00968a..8bf22aae15 100644 --- a/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts +++ b/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts @@ -11,12 +11,10 @@ import { IQueryModelService } from 'sql/workbench/services/query/common/queryMod import { IEncodingSupport, EncodingMode } from 'vs/workbench/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -type PublicPart = { [K in keyof T]: T[K] }; - -export class UntitledQueryEditorInput extends QueryEditorInput implements IEncodingSupport, PublicPart { +export class UntitledQueryEditorInput extends QueryEditorInput implements IEncodingSupport { public static readonly ID = 'workbench.editorInput.untitledQueryInput'; diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index 4ef6980ecc..37d9e80ec6 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -32,7 +32,7 @@ import { URI } from 'vs/base/common/uri'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; suite('SQL QueryAction Tests', () => { diff --git a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts index bd58cfb784..59729e5412 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts @@ -20,7 +20,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { TestStorageService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput'; import { TestQueryModelService } from 'sql/workbench/services/query/test/common/testQueryModelService'; import { Event } from 'vs/base/common/event'; diff --git a/src/sql/workbench/contrib/query/test/browser/queryInputFactory.test.ts b/src/sql/workbench/contrib/query/test/browser/queryInputFactory.test.ts index 0449cbb07f..0b10557aa2 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryInputFactory.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryInputFactory.test.ts @@ -19,7 +19,7 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionManagementService, IConnectionCompletionOptions, IConnectionCallbacks, IConnectionResult } from 'sql/platform/connection/common/connectionManagement'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; suite('Query Input Factory', () => { diff --git a/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts b/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts index b9205e8cd4..643f7225ad 100644 --- a/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts +++ b/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts @@ -7,7 +7,7 @@ import { EditorInput, EditorModel, IEditorInput } from 'vs/workbench/common/edit import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILanguageAssociation } from 'sql/workbench/services/languageAssociation/common/languageAssociation'; diff --git a/src/sql/workbench/services/languageAssociation/common/doHandleUpgrade.ts b/src/sql/workbench/services/languageAssociation/common/doHandleUpgrade.ts index b1df72855c..79eb191c43 100644 --- a/src/sql/workbench/services/languageAssociation/common/doHandleUpgrade.ts +++ b/src/sql/workbench/services/languageAssociation/common/doHandleUpgrade.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { EditorInput } from 'vs/workbench/common/editor'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { Extensions as ILanguageAssociationExtensions, ILanguageAssociationRegistry } from 'sql/workbench/services/languageAssociation/common/languageAssociation'; import { Registry } from 'vs/platform/registry/common/platform'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; const languageRegistry = Registry.as(ILanguageAssociationExtensions.LanguageAssociations); diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts index d078f248c4..0dd139e36b 100644 --- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts +++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts @@ -140,7 +140,7 @@ export class QueryEditorService implements IQueryEditorService { let counter = 1; // Get document name and check if it exists let filePath = prefixFileName(counter); - while (this._untitledEditorService.exists(URI.from({ scheme: Schemas.untitled, path: filePath }))) { + while (this._untitledEditorService.get(URI.from({ scheme: Schemas.untitled, path: filePath }))) { counter++; filePath = prefixFileName(counter); } diff --git a/src/vs/base/browser/globalMouseMoveMonitor.ts b/src/vs/base/browser/globalMouseMoveMonitor.ts index 5265d2fb1b..014a4ec78e 100644 --- a/src/vs/base/browser/globalMouseMoveMonitor.ts +++ b/src/vs/base/browser/globalMouseMoveMonitor.ts @@ -5,6 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; +import * as browser from 'vs/base/browser/browser'; import { IframeUtils } from 'vs/base/browser/iframe'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -102,7 +103,7 @@ export class GlobalMouseMoveMonitor implements I for (const element of listenTo) { this._hooks.add(dom.addDisposableThrottledListener(element, mouseMove, (data: R) => { - if (data.buttons !== initialButtons) { + if (!browser.isIE && data.buttons !== initialButtons) { // Buttons state has changed in the meantime this.stopMonitoring(true); return; diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 9d2cdac5e2..c7f1b794f8 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings'; import { IActionRunner, IAction, Action, IActionViewItem } from 'vs/base/common/actions'; import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; -import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses } from 'vs/base/browser/dom'; +import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -464,7 +464,13 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } updateLabel(): void { + if (!this.label) { + return; + } + if (this.options.label) { + clearNode(this.label); + let label = this.getAction().label; if (label) { const cleanLabel = cleanMnemonic(label); @@ -472,9 +478,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { label = cleanLabel; } - if (this.label) { - this.label.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&')); - } + this.label.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&')); const matches = MENU_MNEMONIC_REGEX.exec(label); @@ -490,22 +494,25 @@ class BaseMenuActionViewItem extends BaseActionViewItem { escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label); } + const replaceDoubleEscapes = (str: string) => str.replace(/&&/g, '&'); + if (escMatch) { - label = `${label.substr(0, escMatch.index)}${label.substr(escMatch.index + escMatch[0].length)}`; + this.label.append( + strings.ltrim(replaceDoubleEscapes(label.substr(0, escMatch.index)), ' '), + $('u', { 'aria-hidden': 'true' }, + escMatch[3]), + strings.rtrim(replaceDoubleEscapes(label.substr(escMatch.index + escMatch[0].length)), ' ')); + } else { + this.label.innerText = replaceDoubleEscapes(label).trim(); } - label = label.replace(/&&/g, '&'); if (this.item) { this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase()); } } else { - label = label.replace(/&&/g, '&'); + this.label.innerText = label.replace(/&&/g, '&').trim(); } } - - if (this.label) { - this.label.innerHTML = label.trim(); - } } } diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 424d2d1ba3..0d5664d9ef 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -360,7 +360,7 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o } // Flush contents (not metadata) of the file to disk - fs.fdatasync(fd, (syncError: Error) => { + fs.fdatasync(fd, (syncError: Error | null) => { // In some exotic setups it is well possible that node fails to sync // In that case we disable flushing and warn to the console diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index 3557b9f594..5fc3ef43f1 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -409,7 +409,7 @@ export function createQueuedSender(childProcess: cp.ChildProcess): IQueuedSender return; } - const result = childProcess.send(msg, (error: Error) => { + const result = childProcess.send(msg, (error: Error | null) => { if (error) { console.error(error); // unlikely to happen, best we can do is log this error } diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index f299061f89..3806103511 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -440,7 +440,7 @@ export class IssueReporter extends Disposable { sendWorkbenchCommand('workbench.action.reloadWindowWithExtensionsDisabled'); }); - this.addEventListener('extensionBugsLink', 'click', (e: MouseEvent) => { + this.addEventListener('extensionBugsLink', 'click', (e: Event) => { const url = (e.target).innerText; shell.openExternal(url); }); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 1e70210593..d2457db13a 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -12,7 +12,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { ExtensionManagementChannel, GlobalExtensionEnablementServiceClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -49,7 +49,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAuthTokenService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAuthTokenServiceChannel, UserDataAutoSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; @@ -61,6 +61,11 @@ import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentia import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-browser/userDataAutoSyncService'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { UserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataAuthTokenService'; +import { NativeStorageService } from 'vs/platform/storage/node/storageService'; +import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -100,7 +105,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const onExit = () => disposables.dispose(); process.once('exit', onExit); - ipcRenderer.once('handshake:goodbye', onExit); + ipcRenderer.once('electron-main->shared-process: exit', onExit); disposables.add(server); @@ -119,6 +124,11 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat disposables.add(configurationService); await configurationService.initialize(); + const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService); + await storageService.initialize(); + services.set(IStorageService, storageService); + disposables.add(toDisposable(() => storageService.flush())); + services.set(IEnvironmentService, environmentService); services.set(IProductService, { _serviceBrand: undefined, ...product }); services.set(ILogService, logService); @@ -147,7 +157,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat instantiationService.invokeFunction(accessor => { const services = new ServiceCollection(); const environmentService = accessor.get(IEnvironmentService); - const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; + const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; const telemetryLogService = new FollowerLogService(loggerClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.'); telemetryLogService.info('==========================================================='); @@ -181,8 +191,9 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataAuthTokenService, new SyncDescriptor(UserDataAuthTokenService)); services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); - services.set(IGlobalExtensionEnablementService, new GlobalExtensionEnablementServiceClient(server.getChannel('globalExtensionEnablement', client => client.ctx !== 'main'))); + services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); + services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService)); services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); @@ -271,13 +282,20 @@ function setupIPC(hook: string): Promise { } async function handshake(configuration: ISharedProcessConfiguration): Promise { + + // receive payload from electron-main to start things const data = await new Promise(c => { - ipcRenderer.once('handshake:hey there', (_: any, r: ISharedProcessInitData) => c(r)); - ipcRenderer.send('handshake:hello'); + ipcRenderer.once('electron-main->shared-process: payload', (_: any, r: ISharedProcessInitData) => c(r)); + + // tell electron-main we are ready to receive payload + ipcRenderer.send('shared-process->electron-main: ready-for-payload'); }); + // await IPC connection and signal this back to electron-main const server = await setupIPC(data.sharedIPCHandle); + ipcRenderer.send('shared-process->electron-main: ipc-ready'); + // await initialization and signal this back to electron-main await main(server, data, configuration); - ipcRenderer.send('handshake:im ready'); + ipcRenderer.send('shared-process->electron-main: init-done'); } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index e149132b4f..28911ff396 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -364,7 +364,14 @@ export class CodeApplication extends Disposable { // Spawn shared process after the first window has opened and 3s have passed const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); - const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); + const sharedProcessClient = sharedProcess.whenIpcReady().then(() => { + this.logService.trace('Shared process: IPC ready'); + return connect(this.environmentService.sharedIPCHandle, 'main'); + }); + const sharedProcessReady = sharedProcess.whenReady().then(() => { + this.logService.trace('Shared process: init ready'); + return sharedProcessClient; + }); this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { this._register(new RunOnceScheduler(async () => { const userEnv = await getShellEnvironment(this.logService, this.environmentService); @@ -374,7 +381,7 @@ export class CodeApplication extends Disposable { }); // Services - const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessClient); + const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessReady); // Create driver if (this.environmentService.driverHandle) { @@ -391,7 +398,7 @@ export class CodeApplication extends Disposable { const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient)); // Post Open Windows Tasks - this.afterWindowOpen(); + appInstantiationService.invokeFunction(this.afterWindowOpen.bind(this)); // Tracing: Stop tracing after windows are ready if enabled if (this.environmentService.args.trace) { @@ -424,7 +431,7 @@ export class CodeApplication extends Disposable { return { machineId, trueMachineId }; } - private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessClient: Promise>): Promise { + private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessReady: Promise>): Promise { const services = new ServiceCollection(); const fileService = this._register(new FileService(this.logService)); @@ -456,7 +463,7 @@ export class CodeApplication extends Disposable { services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess])); services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); - const diagnosticsChannel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('diagnostics'))); + const diagnosticsChannel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))); services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel])); services.set(IIssueService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv])); @@ -477,7 +484,7 @@ export class CodeApplication extends Disposable { // Telemetry if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { - const channel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('telemetryAppender'))); + const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)); const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath); const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot]; @@ -571,6 +578,7 @@ export class CodeApplication extends Disposable { const storageMainService = accessor.get(IStorageMainService); const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService)); electronIpcServer.registerChannel('storage', storageChannel); + sharedProcessClient.then(client => client.registerChannel('storage', storageChannel)); const loggerChannel = new LoggerChannel(accessor.get(ILogService)); electronIpcServer.registerChannel('logger', loggerChannel); @@ -706,13 +714,18 @@ export class CodeApplication extends Disposable { return { fileUri: URI.file(path) }; } - private afterWindowOpen(): void { - + private afterWindowOpen(accessor: ServicesAccessor): void { // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; // Remote Authorities this.handleRemoteAuthorities(); + + // Initialize update service + const updateService = accessor.get(IUpdateService); + if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) { + updateService.initialize(); + } } private handleRemoteAuthorities(): void { diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 6ebf353f02..a313d6fced 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -21,6 +21,8 @@ export class SharedProcess implements ISharedProcess { private window: BrowserWindow | null = null; + private readonly _whenReady: Promise; + constructor( private readonly machineId: string, private userEnv: NodeJS.ProcessEnv, @@ -28,10 +30,13 @@ export class SharedProcess implements ISharedProcess { @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, @IThemeMainService private readonly themeMainService: IThemeMainService - ) { } + ) { + // overall ready promise when shared process signals initialization is done + this._whenReady = new Promise(c => ipcMain.once('shared-process->electron-main: init-done', () => c(undefined))); + } @memoize - private get _whenReady(): Promise { + private get _whenIpcReady(): Promise { this.window = new BrowserWindow({ show: false, backgroundColor: this.themeMainService.getBackgroundColor(), @@ -98,16 +103,19 @@ export class SharedProcess implements ISharedProcess { }); return new Promise(c => { - const onHello = Event.once(Event.fromNodeEventEmitter(ipcMain, 'handshake:hello', ({ sender }: { sender: WebContents }) => sender)); - disposables.add(onHello(sender => { - sender.send('handshake:hey there', { + // send payload once shared process is ready to receive it + disposables.add(Event.once(Event.fromNodeEventEmitter(ipcMain, 'shared-process->electron-main: ready-for-payload', ({ sender }: { sender: WebContents }) => sender))(sender => { + sender.send('electron-main->shared-process: payload', { sharedIPCHandle: this.environmentService.sharedIPCHandle, args: this.environmentService.args, logLevel: this.logService.getLevel() }); - disposables.add(toDisposable(() => sender.send('handshake:goodbye'))); - ipcMain.once('handshake:im ready', () => c(undefined)); + // signal exit to shared process when we get disposed + disposables.add(toDisposable(() => sender.send('electron-main->shared-process: exit'))); + + // complete IPC-ready promise when shared process signals this to us + ipcMain.once('shared-process->electron-main: ipc-ready', () => c(undefined)); })); }); } @@ -122,6 +130,11 @@ export class SharedProcess implements ISharedProcess { await this._whenReady; } + async whenIpcReady(): Promise { + await this.barrier.wait(); + await this._whenIpcReady; + } + toggle(): void { if (!this.window || this.window.isVisible()) { this.hide(); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index c3635ab895..3d8474347b 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -331,7 +331,7 @@ export async function main(argv: ParsedArgs): Promise { const envService = accessor.get(IEnvironmentService); const stateService = accessor.get(IStateService); - const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService; + const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService; const services = new ServiceCollection(); diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index 2447f36936..b92c5b2ad5 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -88,6 +88,7 @@ export interface ISerializedFontInfo { readonly canUseHalfwidthRightwardsArrow: boolean; readonly spaceWidth: number; middotWidth: number; + wsmiddotWidth: number; readonly maxDigitWidth: number; } @@ -161,6 +162,7 @@ class CSSBasedConfiguration extends Disposable { // compatibility with older versions of VS Code which did not store this... savedFontInfo.fontFeatureSettings = savedFontInfo.fontFeatureSettings || EditorFontLigatures.OFF; savedFontInfo.middotWidth = savedFontInfo.middotWidth || savedFontInfo.spaceWidth; + savedFontInfo.wsmiddotWidth = savedFontInfo.wsmiddotWidth || savedFontInfo.spaceWidth; const fontInfo = new FontInfo(savedFontInfo, false); this._writeToCache(fontInfo, fontInfo); } @@ -186,6 +188,7 @@ class CSSBasedConfiguration extends Disposable { canUseHalfwidthRightwardsArrow: readConfig.canUseHalfwidthRightwardsArrow, spaceWidth: Math.max(readConfig.spaceWidth, 5), middotWidth: Math.max(readConfig.middotWidth, 5), + wsmiddotWidth: Math.max(readConfig.wsmiddotWidth, 5), maxDigitWidth: Math.max(readConfig.maxDigitWidth, 5), }, false); } @@ -226,9 +229,12 @@ class CSSBasedConfiguration extends Disposable { const rightwardsArrow = this.createRequest('→', CharWidthRequestType.Regular, all, monospace); const halfwidthRightwardsArrow = this.createRequest('→', CharWidthRequestType.Regular, all, null); - // middle dot character + // U+00B7 - MIDDLE DOT const middot = this.createRequest('·', CharWidthRequestType.Regular, all, monospace); + // U+2E31 - WORD SEPARATOR MIDDLE DOT + const wsmiddotWidth = this.createRequest(String.fromCharCode(0x2E31), CharWidthRequestType.Regular, all, null); + // monospace test: some characters this.createRequest('|', CharWidthRequestType.Regular, all, monospace); this.createRequest('/', CharWidthRequestType.Regular, all, monospace); @@ -294,6 +300,7 @@ class CSSBasedConfiguration extends Disposable { canUseHalfwidthRightwardsArrow: canUseHalfwidthRightwardsArrow, spaceWidth: space.width, middotWidth: middot.width, + wsmiddotWidth: wsmiddotWidth.width, maxDigitWidth: maxDigitWidth }, canTrustBrowserZoomLevel); } @@ -379,7 +386,11 @@ export class Configuration extends CommonEditorConfiguration { emptySelectionClipboard: browser.isWebKit || browser.isFirefox, pixelRatio: browser.getPixelRatio(), zoomLevel: browser.getZoomLevel(), - accessibilitySupport: this.accessibilityService.isScreenReaderOptimized() ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled + accessibilitySupport: ( + this.accessibilityService.isScreenReaderOptimized() + ? AccessibilitySupport.Enabled + : this.accessibilityService.getAccessibilitySupport() + ) }; } diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 00d6b566ee..6903e61dfa 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -57,19 +57,6 @@ export interface ITextAreaInputHost { deduceModelPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position; } -const enum TextAreaInputEventType { - none, - compositionstart, - compositionupdate, - compositionend, - input, - cut, - copy, - paste, - focus, - blur -} - interface CompositionEvent extends UIEvent { readonly data: string; readonly locale: string; @@ -155,7 +142,6 @@ export class TextAreaInput extends Disposable { private readonly _host: ITextAreaInputHost; private readonly _textArea: TextAreaWrapper; - private _lastTextAreaEvent: TextAreaInputEventType; private readonly _asyncTriggerCut: RunOnceScheduler; private _textAreaState: TextAreaState; @@ -169,7 +155,6 @@ export class TextAreaInput extends Disposable { super(); this._host = host; this._textArea = this._register(new TextAreaWrapper(textArea)); - this._lastTextAreaEvent = TextAreaInputEventType.none; this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0)); this._textAreaState = TextAreaState.EMPTY; @@ -200,8 +185,6 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'compositionstart', (e: CompositionEvent) => { - this._lastTextAreaEvent = TextAreaInputEventType.compositionstart; - if (this._isDoingComposition) { return; } @@ -218,10 +201,10 @@ export class TextAreaInput extends Disposable { /** * Deduce the typed input from a text area's value and the last observed state. */ - const deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean, couldBeTypingAtOffset0: boolean): [TextAreaState, ITypeData] => { + const deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean): [TextAreaState, ITypeData] => { const oldState = this._textAreaState; const newState = TextAreaState.readFromTextArea(this._textArea); - return [newState, TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput, couldBeTypingAtOffset0)]; + return [newState, TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput)]; }; /** @@ -258,10 +241,8 @@ export class TextAreaInput extends Disposable { }; this._register(dom.addDisposableListener(textArea.domNode, 'compositionupdate', (e: CompositionEvent) => { - this._lastTextAreaEvent = TextAreaInputEventType.compositionupdate; - if (compositionDataInValid(e.locale)) { - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false); + const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false); this._textAreaState = newState; this._onType.fire(typeInput); this._onCompositionUpdate.fire(e); @@ -275,7 +256,6 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'compositionend', (e: CompositionEvent) => { - this._lastTextAreaEvent = TextAreaInputEventType.compositionend; // https://github.com/microsoft/monaco-editor/issues/1663 // On iOS 13.2, Chinese system IME randomly trigger an additional compositionend event with empty data if (!this._isDoingComposition) { @@ -283,7 +263,7 @@ export class TextAreaInput extends Disposable { } if (compositionDataInValid(e.locale)) { // https://github.com/Microsoft/monaco-editor/issues/339 - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false); + const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false); this._textAreaState = newState; this._onType.fire(typeInput); } else { @@ -307,10 +287,6 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'input', () => { - // We want to find out if this is the first `input` after a `focus`. - const previousEventWasFocus = (this._lastTextAreaEvent === TextAreaInputEventType.focus); - this._lastTextAreaEvent = TextAreaInputEventType.input; - // Pretend here we touched the text area, as the `input` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received input event'); @@ -319,7 +295,7 @@ export class TextAreaInput extends Disposable { return; } - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/platform.isMacintosh, /*couldBeTypingAtOffset0*/previousEventWasFocus && platform.isMacintosh); + const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/platform.isMacintosh); if (typeInput.replaceCharCnt === 0 && typeInput.text.length === 1 && strings.isHighSurrogate(typeInput.text.charCodeAt(0))) { // Ignore invalid input but keep it around for next time return; @@ -341,8 +317,6 @@ export class TextAreaInput extends Disposable { // --- Clipboard operations this._register(dom.addDisposableListener(textArea.domNode, 'cut', (e: ClipboardEvent) => { - this._lastTextAreaEvent = TextAreaInputEventType.cut; - // Pretend here we touched the text area, as the `cut` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received cut event'); @@ -352,14 +326,10 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'copy', (e: ClipboardEvent) => { - this._lastTextAreaEvent = TextAreaInputEventType.copy; - this._ensureClipboardGetsEditorSelection(e); })); this._register(dom.addDisposableListener(textArea.domNode, 'paste', (e: ClipboardEvent) => { - this._lastTextAreaEvent = TextAreaInputEventType.paste; - // Pretend here we touched the text area, as the `paste` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received paste event'); @@ -379,11 +349,9 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'focus', () => { - this._lastTextAreaEvent = TextAreaInputEventType.focus; this._setHasFocus(true); })); this._register(dom.addDisposableListener(textArea.domNode, 'blur', () => { - this._lastTextAreaEvent = TextAreaInputEventType.blur; this._setHasFocus(false); })); } @@ -625,7 +593,8 @@ class ClipboardEventUtils { if ((window).clipboardData) { e.preventDefault(); - return (window).clipboardData.getData('Text'); + const text: string = (window).clipboardData.getData('Text'); + return [text, null]; } throw new Error('ClipboardEventUtils.getTextData: Cannot use text data!'); diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts index 76e07f80d8..5847e70f74 100644 --- a/src/vs/editor/browser/controller/textAreaState.ts +++ b/src/vs/editor/browser/controller/textAreaState.ts @@ -96,7 +96,7 @@ export class TextAreaState { return new TextAreaState(text, 0, text.length, null, null); } - public static deduceInput(previousState: TextAreaState, currentState: TextAreaState, couldBeEmojiInput: boolean, couldBeTypingAtOffset0: boolean): ITypeData { + public static deduceInput(previousState: TextAreaState, currentState: TextAreaState, couldBeEmojiInput: boolean): ITypeData { if (!previousState) { // This is the EMPTY state return { @@ -116,18 +116,6 @@ export class TextAreaState { let currentSelectionStart = currentState.selectionStart; let currentSelectionEnd = currentState.selectionEnd; - if (couldBeTypingAtOffset0 && previousValue.length > 0 && previousSelectionStart === previousSelectionEnd && currentSelectionStart === currentSelectionEnd) { - // See https://github.com/Microsoft/vscode/issues/42251 - // where typing always happens at offset 0 in the textarea - // when using a custom title area in OSX and moving the window - if (!strings.startsWith(currentValue, previousValue) && strings.endsWith(currentValue, previousValue)) { - // Looks like something was typed at offset 0 - // ==> pretend we placed the cursor at offset 0 to begin with... - previousSelectionStart = 0; - previousSelectionEnd = 0; - } - } - // Strip the previous suffix from the value (without interfering with the current selection) const previousSuffix = previousValue.substring(previousSelectionEnd); const currentSuffix = currentValue.substring(currentSelectionEnd); diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 4defe1e100..285dec33ff 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -74,6 +74,7 @@ export class ViewLineOptions { public readonly renderControlCharacters: boolean; public readonly spaceWidth: number; public readonly middotWidth: number; + public readonly wsmiddotWidth: number; public readonly useMonospaceOptimizations: boolean; public readonly canUseHalfwidthRightwardsArrow: boolean; public readonly lineHeight: number; @@ -88,6 +89,7 @@ export class ViewLineOptions { this.renderControlCharacters = options.get(EditorOption.renderControlCharacters); this.spaceWidth = fontInfo.spaceWidth; this.middotWidth = fontInfo.middotWidth; + this.wsmiddotWidth = fontInfo.wsmiddotWidth; this.useMonospaceOptimizations = ( fontInfo.isMonospace && !options.get(EditorOption.disableMonospaceOptimizations) @@ -105,6 +107,7 @@ export class ViewLineOptions { && this.renderControlCharacters === other.renderControlCharacters && this.spaceWidth === other.spaceWidth && this.middotWidth === other.middotWidth + && this.wsmiddotWidth === other.wsmiddotWidth && this.useMonospaceOptimizations === other.useMonospaceOptimizations && this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow && this.lineHeight === other.lineHeight @@ -219,6 +222,7 @@ export class ViewLine implements IVisibleLine { lineData.startVisibleColumn, options.spaceWidth, options.middotWidth, + options.wsmiddotWidth, options.stopRenderingLineAfter, options.renderWhitespace, options.renderControlCharacters, diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index c2e3889c3d..170e1774bc 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { ISashEvent, IVerticalSashLayoutProvider, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; -import { RunOnceScheduler, IntervalTimer } from 'vs/base/common/async'; +import { RunOnceScheduler } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -48,6 +48,7 @@ import { Constants } from 'vs/base/common/uint'; import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; +import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; interface IEditorDiffDecorations { decorations: IModelDeltaDecoration[]; @@ -179,10 +180,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private readonly _overviewDomElement: HTMLElement; private readonly _overviewViewportDomElement: FastDomNode; - private _width: number; - private _height: number; - private _reviewHeight: number; - private readonly _measureDomElementToken: IntervalTimer | null; + private readonly _elementSizeObserver: ElementSizeObserver; private readonly originalEditor: CodeEditorWidget; private readonly _originalDomNode: HTMLElement; @@ -329,9 +327,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._isVisible = true; this._isHandlingScrollEvent = false; - this._width = 0; - this._height = 0; - this._reviewHeight = 0; + this._elementSizeObserver = this._register(new ElementSizeObserver(this._containerDomElement, undefined, () => this._onDidContainerSizeChanged())); + if (options.automaticLayout) { + this._elementSizeObserver.startObserving(); + } this._diffComputationResult = null; @@ -360,12 +359,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._containerDomElement.appendChild(this._reviewPane.shadow.domNode); this._containerDomElement.appendChild(this._reviewPane.actionBarContainer.domNode); - if (options.automaticLayout) { - this._measureDomElementToken = new IntervalTimer(); - this._measureDomElementToken.cancelAndSet(() => this._measureDomElement(false), 100); - } else { - this._measureDomElementToken = null; - } + // enableSplitViewResizing this._enableSplitViewResizing = true; @@ -563,10 +557,6 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._beginUpdateDecorationsTimeout = -1; } - if (this._measureDomElementToken) { - this._measureDomElementToken.dispose(); - } - this._cleanViewZonesAndDecorations(); if (this._originalOverviewRuler) { @@ -866,7 +856,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public layout(dimension?: editorCommon.IDimension): void { - this._measureDomElement(false, dimension); + this._elementSizeObserver.observe(dimension); } public focus(): void { @@ -907,35 +897,21 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE //------------ begin layouting methods - private _measureDomElement(forceDoLayoutCall: boolean, dimensions?: editorCommon.IDimension): void { - dimensions = dimensions || { - width: this._containerDomElement.clientWidth, - height: this._containerDomElement.clientHeight - }; - - if (dimensions.width <= 0) { - this._width = 0; - this._height = 0; - this._reviewHeight = 0; - return; - } - - if (!forceDoLayoutCall && dimensions.width === this._width && dimensions.height === this._height) { - // Nothing has changed - return; - } - - this._width = dimensions.width; - this._height = dimensions.height; - this._reviewHeight = this._reviewPane.isVisible() ? this._height : 0; - + private _onDidContainerSizeChanged(): void { this._doLayout(); } + private _getReviewHeight(): number { + return this._reviewPane.isVisible() ? this._elementSizeObserver.getHeight() : 0; + } + private _layoutOverviewRulers(): void { if (!this._originalOverviewRuler || !this._modifiedOverviewRuler) { return; } + const height = this._elementSizeObserver.getHeight(); + const reviewHeight = this._getReviewHeight(); + let freeSpace = DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * DiffEditorWidget.ONE_OVERVIEW_WIDTH; let layoutInfo = this.modifiedEditor.getLayoutInfo(); if (layoutInfo) { @@ -943,13 +919,13 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE top: 0, width: DiffEditorWidget.ONE_OVERVIEW_WIDTH, right: freeSpace + DiffEditorWidget.ONE_OVERVIEW_WIDTH, - height: (this._height - this._reviewHeight) + height: (height - reviewHeight) }); this._modifiedOverviewRuler.setLayout({ top: 0, right: 0, width: DiffEditorWidget.ONE_OVERVIEW_WIDTH, - height: (this._height - this._reviewHeight) + height: (height - reviewHeight) }); } } @@ -1095,33 +1071,38 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public doLayout(): void { - this._measureDomElement(true); + this._elementSizeObserver.observe(); + this._doLayout(); } private _doLayout(): void { + const width = this._elementSizeObserver.getWidth(); + const height = this._elementSizeObserver.getHeight(); + const reviewHeight = this._getReviewHeight(); + let splitPoint = this._strategy.layout(); this._originalDomNode.style.width = splitPoint + 'px'; this._originalDomNode.style.left = '0px'; - this._modifiedDomNode.style.width = (this._width - splitPoint) + 'px'; + this._modifiedDomNode.style.width = (width - splitPoint) + 'px'; this._modifiedDomNode.style.left = splitPoint + 'px'; this._overviewDomElement.style.top = '0px'; - this._overviewDomElement.style.height = (this._height - this._reviewHeight) + 'px'; + this._overviewDomElement.style.height = (height - reviewHeight) + 'px'; this._overviewDomElement.style.width = DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH + 'px'; - this._overviewDomElement.style.left = (this._width - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH) + 'px'; + this._overviewDomElement.style.left = (width - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH) + 'px'; this._overviewViewportDomElement.setWidth(DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH); this._overviewViewportDomElement.setHeight(30); - this.originalEditor.layout({ width: splitPoint, height: (this._height - this._reviewHeight) }); - this.modifiedEditor.layout({ width: this._width - splitPoint - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH, height: (this._height - this._reviewHeight) }); + this.originalEditor.layout({ width: splitPoint, height: (height - reviewHeight) }); + this.modifiedEditor.layout({ width: width - splitPoint - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH, height: (height - reviewHeight) }); if (this._originalOverviewRuler || this._modifiedOverviewRuler) { this._layoutOverviewRulers(); } - this._reviewPane.layout(this._height - this._reviewHeight, this._width, this._reviewHeight); + this._reviewPane.layout(height - reviewHeight, width, reviewHeight); this._layoutOverviewViewport(); } @@ -1162,11 +1143,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _createDataSource(): IDataSource { return { getWidth: () => { - return this._width; + return this._elementSizeObserver.getWidth(); }, getHeight: () => { - return (this._height - this._reviewHeight); + return (this._elementSizeObserver.getHeight() - this._getReviewHeight()); }, getContainerDomNode: () => { @@ -1200,7 +1181,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } // Just do a layout, the strategy might need it - this._measureDomElement(true); + this._doLayout(); } private _getLineChangeAtOrBeforeLineNumber(lineNumber: number, startLineNumberExtractor: (lineChange: editorCommon.ILineChange) => number): editorCommon.ILineChange | null { @@ -2181,6 +2162,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { 0, fontInfo.spaceWidth, fontInfo.middotWidth, + fontInfo.wsmiddotWidth, options.get(EditorOption.stopRenderingLineAfter), options.get(EditorOption.renderWhitespace), options.get(EditorOption.renderControlCharacters), diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 7779d8ea5d..ce79be923e 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -783,6 +783,7 @@ export class DiffReview extends Disposable { 0, fontInfo.spaceWidth, fontInfo.middotWidth, + fontInfo.wsmiddotWidth, options.get(EditorOption.stopRenderingLineAfter), options.get(EditorOption.renderWhitespace), options.get(EditorOption.renderControlCharacters), diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 8d2897733b..3807fd74a8 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -366,6 +366,10 @@ export interface IEditorOptions { * Defaults to 10 (ms) */ quickSuggestionsDelay?: number; + /** + * Controls the spacing around the editor. + */ + padding?: IEditorPaddingOptions; /** * Parameter hint options. */ @@ -2082,6 +2086,65 @@ function _multiCursorModifierFromString(multiCursorModifier: 'ctrlCmd' | 'alt'): //#endregion +//#region padding + +/** + * Configuration options for editor padding + */ +export interface IEditorPaddingOptions { + /** + * Spacing between top edge of editor and first line. + */ + top?: number; + /** + * Spacing between bottom edge of editor and last line. + */ + bottom?: number; +} + +export interface InternalEditorPaddingOptions { + readonly top: number; + readonly bottom: number; +} + +class EditorPadding extends BaseEditorOption { + + constructor() { + super( + EditorOption.padding, 'padding', { top: 0, bottom: 0 }, + { + 'editor.padding.top': { + type: 'number', + default: 0, + minimum: 0, + maximum: 1000, + description: nls.localize('padding.top', "Controls the amount of space between the top edge of the editor and the first line.") + }, + 'editor.padding.bottom': { + type: 'number', + default: 0, + minimum: 0, + maximum: 1000, + description: nls.localize('padding.bottom', "Controls the amount of space between the bottom edge of the editor and the last line.") + } + } + ); + } + + public validate(_input: any): InternalEditorPaddingOptions { + if (typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IEditorPaddingOptions; + + return { + top: EditorIntOption.clampedInt(input.top, 0, 0, 1000), + bottom: EditorIntOption.clampedInt(input.bottom, 0, 0, 1000) + }; + } +} +//#endregion + //#region parameterHints /** @@ -3188,6 +3251,7 @@ export const enum EditorOption { occurrencesHighlight, overviewRulerBorder, overviewRulerLanes, + padding, parameterHints, peekWidgetDefaultFocus, definitionLinkOpensInPeek, @@ -3575,6 +3639,7 @@ export const EditorOptions = { EditorOption.overviewRulerLanes, 'overviewRulerLanes', 3, 0, 3 )), + padding: register(new EditorPadding()), parameterHints: register(new EditorParameterHints()), peekWidgetDefaultFocus: register(new EditorStringEnumOption( EditorOption.peekWidgetDefaultFocus, 'peekWidgetDefaultFocus', @@ -3634,7 +3699,7 @@ export const EditorOptions = { )), renderWhitespace: register(new EditorStringEnumOption( EditorOption.renderWhitespace, 'renderWhitespace', - 'none' as 'none' | 'boundary' | 'selection' | 'all', + 'selection' as 'selection' | 'none' | 'boundary' | 'all', ['none', 'boundary', 'selection', 'all'] as const, { enumDescriptions: [ diff --git a/src/vs/editor/common/config/fontInfo.ts b/src/vs/editor/common/config/fontInfo.ts index b8b6311e63..1a4d5fa9c7 100644 --- a/src/vs/editor/common/config/fontInfo.ts +++ b/src/vs/editor/common/config/fontInfo.ts @@ -135,6 +135,7 @@ export class FontInfo extends BareFontInfo { readonly canUseHalfwidthRightwardsArrow: boolean; readonly spaceWidth: number; readonly middotWidth: number; + readonly wsmiddotWidth: number; readonly maxDigitWidth: number; /** @@ -154,6 +155,7 @@ export class FontInfo extends BareFontInfo { canUseHalfwidthRightwardsArrow: boolean; spaceWidth: number; middotWidth: number; + wsmiddotWidth: number; maxDigitWidth: number; }, isTrusted: boolean) { super(opts); @@ -164,6 +166,7 @@ export class FontInfo extends BareFontInfo { this.canUseHalfwidthRightwardsArrow = opts.canUseHalfwidthRightwardsArrow; this.spaceWidth = opts.spaceWidth; this.middotWidth = opts.middotWidth; + this.wsmiddotWidth = opts.wsmiddotWidth; this.maxDigitWidth = opts.maxDigitWidth; } @@ -183,6 +186,7 @@ export class FontInfo extends BareFontInfo { && this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow && this.spaceWidth === other.spaceWidth && this.middotWidth === other.middotWidth + && this.wsmiddotWidth === other.wsmiddotWidth && this.maxDigitWidth === other.maxDigitWidth ); } diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 7d4de346ef..ba7e097210 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -61,6 +61,11 @@ export interface ITextEditorModel extends IEditorModel { * Figure out if this model is resolved or not. */ isResolved(): this is IResolvedTextEditorModel; + + /** + * The mode id of the text model if known. + */ + getMode(): string | undefined; } export interface IResolvedTextEditorModel extends ITextEditorModel { diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index c876882ebe..b0669a3810 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -229,53 +229,54 @@ export enum EditorOption { occurrencesHighlight = 61, overviewRulerBorder = 62, overviewRulerLanes = 63, - parameterHints = 64, - peekWidgetDefaultFocus = 65, - definitionLinkOpensInPeek = 66, - quickSuggestions = 67, - quickSuggestionsDelay = 68, - readOnly = 69, - renderControlCharacters = 70, - renderIndentGuides = 71, - renderFinalNewline = 72, - renderLineHighlight = 73, - renderValidationDecorations = 74, - renderWhitespace = 75, - revealHorizontalRightPadding = 76, - roundedSelection = 77, - rulers = 78, - scrollbar = 79, - scrollBeyondLastColumn = 80, - scrollBeyondLastLine = 81, - scrollPredominantAxis = 82, - selectionClipboard = 83, - selectionHighlight = 84, - selectOnLineNumbers = 85, - showFoldingControls = 86, - showUnused = 87, - snippetSuggestions = 88, - smoothScrolling = 89, - stopRenderingLineAfter = 90, - suggest = 91, - suggestFontSize = 92, - suggestLineHeight = 93, - suggestOnTriggerCharacters = 94, - suggestSelection = 95, - tabCompletion = 96, - useTabStops = 97, - wordSeparators = 98, - wordWrap = 99, - wordWrapBreakAfterCharacters = 100, - wordWrapBreakBeforeCharacters = 101, - wordWrapColumn = 102, - wordWrapMinified = 103, - wrappingIndent = 104, - wrappingStrategy = 105, - editorClassName = 106, - pixelRatio = 107, - tabFocusMode = 108, - layoutInfo = 109, - wrappingInfo = 110 + padding = 64, + parameterHints = 65, + peekWidgetDefaultFocus = 66, + definitionLinkOpensInPeek = 67, + quickSuggestions = 68, + quickSuggestionsDelay = 69, + readOnly = 70, + renderControlCharacters = 71, + renderIndentGuides = 72, + renderFinalNewline = 73, + renderLineHighlight = 74, + renderValidationDecorations = 75, + renderWhitespace = 76, + revealHorizontalRightPadding = 77, + roundedSelection = 78, + rulers = 79, + scrollbar = 80, + scrollBeyondLastColumn = 81, + scrollBeyondLastLine = 82, + scrollPredominantAxis = 83, + selectionClipboard = 84, + selectionHighlight = 85, + selectOnLineNumbers = 86, + showFoldingControls = 87, + showUnused = 88, + snippetSuggestions = 89, + smoothScrolling = 90, + stopRenderingLineAfter = 91, + suggest = 92, + suggestFontSize = 93, + suggestLineHeight = 94, + suggestOnTriggerCharacters = 95, + suggestSelection = 96, + tabCompletion = 97, + useTabStops = 98, + wordSeparators = 99, + wordWrap = 100, + wordWrapBreakAfterCharacters = 101, + wordWrapBreakBeforeCharacters = 102, + wordWrapColumn = 103, + wordWrapMinified = 104, + wrappingIndent = 105, + wrappingStrategy = 106, + editorClassName = 107, + pixelRatio = 108, + tabFocusMode = 109, + layoutInfo = 110, + wrappingInfo = 111 } /** diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index ca7ca0cf70..d0df762169 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -111,8 +111,10 @@ export class LinesLayout { private _minWidth: number; private _lineCount: number; private _lineHeight: number; + private _paddingTop: number; + private _paddingBottom: number; - constructor(lineCount: number, lineHeight: number) { + constructor(lineCount: number, lineHeight: number, paddingTop: number, paddingBottom: number) { this._instanceId = strings.singleLetterHash(++LinesLayout.INSTANCE_COUNT); this._pendingChanges = new PendingChanges(); this._lastWhitespaceId = 0; @@ -121,6 +123,8 @@ export class LinesLayout { this._minWidth = -1; /* marker for not being computed */ this._lineCount = lineCount; this._lineHeight = lineHeight; + this._paddingTop = paddingTop; + this._paddingBottom = paddingBottom; } /** @@ -158,6 +162,14 @@ export class LinesLayout { this._lineHeight = lineHeight; } + /** + * Changes the padding used to calculate vertical offsets. + */ + public setPadding(paddingTop: number, paddingBottom: number): void { + this._paddingTop = paddingTop; + this._paddingBottom = paddingBottom; + } + /** * Set the number of lines. * @@ -404,7 +416,8 @@ export class LinesLayout { this._checkPendingChanges(); const linesHeight = this._lineHeight * this._lineCount; const whitespacesHeight = this.getWhitespacesTotalHeight(); - return linesHeight + whitespacesHeight; + + return linesHeight + whitespacesHeight + this._paddingTop + this._paddingBottom; } /** @@ -495,7 +508,7 @@ export class LinesLayout { const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber); - return previousLinesHeight + previousWhitespacesHeight; + return previousLinesHeight + previousWhitespacesHeight + this._paddingTop; } /** @@ -719,7 +732,7 @@ export class LinesLayout { } else { previousWhitespacesHeight = 0; } - return previousLinesHeight + previousWhitespacesHeight; + return previousLinesHeight + previousWhitespacesHeight + this._paddingTop; } public getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset: number): number { diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index e7238286d3..f05c28b3ca 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -161,8 +161,9 @@ export class ViewLayout extends Disposable implements IViewLayout { this._configuration = configuration; const options = this._configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); + const padding = options.get(EditorOption.padding); - this._linesLayout = new LinesLayout(lineCount, options.get(EditorOption.lineHeight)); + this._linesLayout = new LinesLayout(lineCount, options.get(EditorOption.lineHeight), padding.top, padding.bottom); this._scrollable = this._register(new EditorScrollable(0, scheduleAtNextAnimationFrame)); this._configureSmoothScrollDuration(); @@ -202,6 +203,10 @@ export class ViewLayout extends Disposable implements IViewLayout { if (e.hasChanged(EditorOption.lineHeight)) { this._linesLayout.setLineHeight(options.get(EditorOption.lineHeight)); } + if (e.hasChanged(EditorOption.padding)) { + const padding = options.get(EditorOption.padding); + this._linesLayout.setPadding(padding.top, padding.bottom); + } if (e.hasChanged(EditorOption.layoutInfo)) { const layoutInfo = options.get(EditorOption.layoutInfo); const width = layoutInfo.contentWidth; diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index ff8c4568b2..27bba3591f 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -68,7 +68,8 @@ export class RenderLineInput { public readonly tabSize: number; public readonly startVisibleColumn: number; public readonly spaceWidth: number; - public readonly middotWidth: number; + public readonly renderSpaceWidth: number; + public readonly renderSpaceCharCode: number; public readonly stopRenderingLineAfter: number; public readonly renderWhitespace: RenderWhitespace; public readonly renderControlCharacters: boolean; @@ -94,6 +95,7 @@ export class RenderLineInput { startVisibleColumn: number, spaceWidth: number, middotWidth: number, + wsmiddotWidth: number, stopRenderingLineAfter: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'all', renderControlCharacters: boolean, @@ -112,7 +114,6 @@ export class RenderLineInput { this.tabSize = tabSize; this.startVisibleColumn = startVisibleColumn; this.spaceWidth = spaceWidth; - this.middotWidth = middotWidth; this.stopRenderingLineAfter = stopRenderingLineAfter; this.renderWhitespace = ( renderWhitespace === 'all' @@ -126,6 +127,16 @@ export class RenderLineInput { this.renderControlCharacters = renderControlCharacters; this.fontLigatures = fontLigatures; this.selectionsOnLine = selectionsOnLine && selectionsOnLine.sort((a, b) => a.startOffset < b.startOffset ? -1 : 1); + + const wsmiddotDiff = Math.abs(wsmiddotWidth - spaceWidth); + const middotDiff = Math.abs(middotWidth - spaceWidth); + if (wsmiddotDiff < middotDiff) { + this.renderSpaceWidth = wsmiddotWidth; + this.renderSpaceCharCode = 0x2E31; // U+2E31 - WORD SEPARATOR MIDDLE DOT + } else { + this.renderSpaceWidth = middotWidth; + this.renderSpaceCharCode = 0xB7; // U+00B7 - MIDDLE DOT + } } private sameSelection(otherSelections: LineRange[] | null): boolean { @@ -162,6 +173,8 @@ export class RenderLineInput { && this.tabSize === other.tabSize && this.startVisibleColumn === other.startVisibleColumn && this.spaceWidth === other.spaceWidth + && this.renderSpaceWidth === other.renderSpaceWidth + && this.renderSpaceCharCode === other.renderSpaceCharCode && this.stopRenderingLineAfter === other.stopRenderingLineAfter && this.renderWhitespace === other.renderWhitespace && this.renderControlCharacters === other.renderControlCharacters @@ -383,7 +396,7 @@ class ResolvedRenderLineInput { public readonly startVisibleColumn: number, public readonly containsRTL: boolean, public readonly spaceWidth: number, - public readonly middotWidth: number, + public readonly renderSpaceCharCode: number, public readonly renderWhitespace: RenderWhitespace, public readonly renderControlCharacters: boolean, ) { @@ -392,7 +405,6 @@ class ResolvedRenderLineInput { } function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput { - const useMonospaceOptimizations = input.useMonospaceOptimizations; const lineContent = input.lineContent; let isOverflowing: boolean; @@ -408,7 +420,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len); if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) { - tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary); + tokens = _applyRenderWhitespace(input, lineContent, len, tokens); } let containsForeignElements = ForeignElementType.None; if (input.lineDecorations.length > 0) { @@ -431,7 +443,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput } return new ResolvedRenderLineInput( - useMonospaceOptimizations, + input.useMonospaceOptimizations, input.canUseHalfwidthRightwardsArrow, lineContent, len, @@ -443,7 +455,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput input.startVisibleColumn, input.containsRTL, input.spaceWidth, - input.middotWidth, + input.renderSpaceCharCode, input.renderWhitespace, input.renderControlCharacters ); @@ -553,7 +565,16 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (→ or ·) do not have the same width as  . * The rendering phase will generate `style="width:..."` for these tokens. */ -function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, startVisibleColumn: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] { +function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len: number, tokens: LinePart[]): LinePart[] { + + const continuesWithWrappedLine = input.continuesWithWrappedLine; + const fauxIndentLength = input.fauxIndentLength; + const tabSize = input.tabSize; + const startVisibleColumn = input.startVisibleColumn; + const useMonospaceOptimizations = input.useMonospaceOptimizations; + const selections = input.selectionsOnLine; + const onlyBoundary = (input.renderWhitespace === RenderWhitespace.Boundary); + const generateLinePartForEachWhitespace = (input.renderSpaceWidth !== input.spaceWidth); let result: LinePart[] = [], resultLen = 0; let tokenIndex = 0; @@ -616,7 +637,14 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW // was in whitespace token if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) { // leaving whitespace token or entering a new indent - result[resultLen++] = new LinePart(charIndex, 'mtkw'); + if (generateLinePartForEachWhitespace) { + const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength); + for (let i = lastEndIndex + 1; i <= charIndex; i++) { + result[resultLen++] = new LinePart(i, 'mtkw'); + } + } else { + result[resultLen++] = new LinePart(charIndex, 'mtkw'); + } tmpIndent = tmpIndent % tabSize; } } else { @@ -661,7 +689,18 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW } } - result[resultLen++] = new LinePart(len, generateWhitespace ? 'mtkw' : tokenType); + if (generateWhitespace) { + if (generateLinePartForEachWhitespace) { + const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength); + for (let i = lastEndIndex + 1; i <= len; i++) { + result[resultLen++] = new LinePart(i, 'mtkw'); + } + } else { + result[resultLen++] = new LinePart(len, 'mtkw'); + } + } else { + result[resultLen++] = new LinePart(len, tokenType); + } return result; } @@ -739,13 +778,10 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render const startVisibleColumn = input.startVisibleColumn; const containsRTL = input.containsRTL; const spaceWidth = input.spaceWidth; - const middotWidth = input.middotWidth; + const renderSpaceCharCode = input.renderSpaceCharCode; const renderWhitespace = input.renderWhitespace; const renderControlCharacters = input.renderControlCharacters; - // use U+2E31 - WORD SEPARATOR MIDDLE DOT or U+00B7 - MIDDLE DOT - const spaceRenderWhitespaceCharacter = (middotWidth > spaceWidth ? 0x2E31 : 0xB7); - const characterMapping = new CharacterMapping(len + 1, parts.length); let charIndex = 0; @@ -815,7 +851,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render } else { // must be CharCode.Space charWidth = 1; - sb.write1(spaceRenderWhitespaceCharacter); // · or word separator middle dot + sb.write1(renderSpaceCharCode); // · or word separator middle dot } charOffsetInPart += charWidth; diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index bf6f54e778..53cdb4b77a 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -611,7 +611,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas return; } else { - const scrollAdjustment = this._getHeight(); + let scrollAdjustment = this._getHeight(); + + // if the editor has top padding, factor that into the zone height + scrollAdjustment -= this._codeEditor.getOption(EditorOption.padding).top; + if (scrollAdjustment <= 0) { + return; + } + viewZone.heightInPx = scrollAdjustment; this._viewZoneId = accessor.addZone(viewZone); diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index e8180d367d..a93633db50 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -304,7 +304,7 @@ export class MarkerController implements IEditorContribution { model.currentMarker = marker; } - private _onMarkerChanged(changedResources: URI[]): void { + private _onMarkerChanged(changedResources: readonly URI[]): void { const editorModel = this._editor.getModel(); if (!editorModel) { return; diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 1117300566..f14803a955 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -134,10 +134,10 @@ class MessageWidget { detailsElement.appendChild(codeElement); } else { this._codeLink = dom.$('a.code-link'); - this._codeLink.setAttribute('href', `${code.link.toString()}`); + this._codeLink.setAttribute('href', `${code.target.toString()}`); this._codeLink.onclick = (e) => { - this._openerService.open(code.link); + this._openerService.open(code.target); e.preventDefault(); e.stopPropagation(); }; diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 04556904f3..43af6f2c8c 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -516,10 +516,10 @@ export class ModesContentHoverWidget extends ContentHoverWidget { sourceElement.innerText = source; } this._codeLink = dom.append(sourceAndCodeElement, $('a.code-link')); - this._codeLink.setAttribute('href', code.link.toString()); + this._codeLink.setAttribute('href', code.target.toString()); this._codeLink.onclick = (e) => { - this._openerService.open(code.link); + this._openerService.open(code.target); e.preventDefault(); e.stopPropagation(); }; diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 8547662cc6..6c65a14042 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -468,6 +468,7 @@ export class SnippetSession { // that ensures the primiary cursor stays primary despite not being // the one with lowest start position edits[idx] = EditOperation.replace(snippetSelection, snippet.toString()); + edits[idx].identifier = { major: idx, minor: 0 }; // mark the edit so only our undo edits will be used to generate end cursors snippets[idx] = new OneSnippet(editor, snippet, offset); } @@ -507,7 +508,11 @@ export class SnippetSession { if (this._snippets[0].hasPlaceholder) { return this._move(true); } else { - return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition())); + return ( + undoEdits + .filter(edit => !!edit.identifier) // only use our undo edits + .map(edit => Selection.fromPositions(edit.range.getEndPosition())) + ); } }); this._editor.revealRange(this._editor.getSelections()[0]); @@ -529,7 +534,11 @@ export class SnippetSession { if (this._snippets[0].hasPlaceholder) { return this._move(undefined); } else { - return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition())); + return ( + undoEdits + .filter(edit => !!edit.identifier) // only use our undo edits + .map(edit => Selection.fromPositions(edit.range.getEndPosition())) + ); } }); } diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index 1c9c0dcbcf..4020ee6f7a 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -11,6 +11,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { NullLogService } from 'vs/platform/log/common/log'; import { Handler } from 'vs/editor/common/editorCommon'; +import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; suite('SnippetController2', function () { @@ -428,4 +429,13 @@ suite('SnippetController2', function () { assertSelections(editor, new Selection(2, 5, 2, 5)); assertContextKeys(contextKeys, false, false, false); }); + + test('issue #90135: confusing trim whitespace edits', function () { + const ctrl = new SnippetController2(editor, logService, contextKeys); + model.setValue(''); + CoreEditingCommands.Tab.runEditorCommand(null, editor, null); + + ctrl.insert('\nfoo'); + assertSelections(editor, new Selection(2, 8, 2, 8)); + }); }); diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index d0a5c2ffbe..c14b089bd4 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -127,6 +127,7 @@ export class Colorizer { 0, 0, 0, + 0, -1, 'none', false, @@ -197,6 +198,7 @@ function _fakeColorize(lines: string[], tabSize: number): string { 0, 0, 0, + 0, -1, 'none', false, @@ -236,6 +238,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: 0, 0, 0, + 0, -1, 'none', false, diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 35c04fb335..da171faea1 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -84,6 +84,10 @@ export class SimpleModel implements IResolvedTextEditorModel { public isResolved(): boolean { return true; } + + public getMode(): string | undefined { + return this.model.getModeId(); + } } export interface IOpenEditorDelegate { diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index e90a8b406e..0a2a0fd1d8 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -90,7 +90,7 @@ export interface IGlobalEditorOptions { tabSize?: number; /** * Insert spaces when pressing `Tab`. - * This setting is overridden based on the file contents when detectIndentation` is on. + * This setting is overridden based on the file contents when `detectIndentation` is on. * Defaults to true. */ insertSpaces?: boolean; diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index ec4c87d1e5..003a608a97 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -501,7 +501,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } let result = line.search(regex); - if (result === -1) { + if (result === -1 || (result !== 0 && rule.matchOnlyAtLineStart)) { continue; } diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index 2029e8fe7f..2a696d9786 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -123,7 +123,7 @@ suite('TextAreaState', () => { textArea.dispose(); }); - function testDeduceInput(prevState: TextAreaState | null, value: string, selectionStart: number, selectionEnd: number, couldBeEmojiInput: boolean, couldBeTypingAtOffset0: boolean, expected: string, expectedCharReplaceCnt: number): void { + function testDeduceInput(prevState: TextAreaState | null, value: string, selectionStart: number, selectionEnd: number, couldBeEmojiInput: boolean, expected: string, expectedCharReplaceCnt: number): void { prevState = prevState || TextAreaState.EMPTY; let textArea = new MockTextAreaWrapper(); @@ -132,7 +132,7 @@ suite('TextAreaState', () => { textArea._selectionEnd = selectionEnd; let newState = TextAreaState.readFromTextArea(textArea); - let actual = TextAreaState.deduceInput(prevState, newState, couldBeEmojiInput, couldBeTypingAtOffset0); + let actual = TextAreaState.deduceInput(prevState, newState, couldBeEmojiInput); assert.equal(actual.text, expected); assert.equal(actual.replaceCharCnt, expectedCharReplaceCnt); @@ -153,7 +153,7 @@ suite('TextAreaState', () => { testDeduceInput( TextAreaState.EMPTY, 's', - 0, 1, true, false, + 0, 1, true, 's', 0 ); @@ -163,7 +163,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('s', 0, 1, null, null), 'せ', - 0, 1, true, false, + 0, 1, true, 'せ', 1 ); @@ -173,7 +173,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せ', 0, 1, null, null), 'せn', - 0, 2, true, false, + 0, 2, true, 'せn', 1 ); @@ -183,7 +183,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せn', 0, 2, null, null), 'せん', - 0, 2, true, false, + 0, 2, true, 'せん', 2 ); @@ -193,7 +193,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せん', 0, 2, null, null), 'せんs', - 0, 3, true, false, + 0, 3, true, 'せんs', 2 ); @@ -203,7 +203,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんs', 0, 3, null, null), 'せんせ', - 0, 3, true, false, + 0, 3, true, 'せんせ', 3 ); @@ -213,7 +213,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせ', 0, 3, null, null), 'せんせ', - 0, 3, true, false, + 0, 3, true, 'せんせ', 3 ); @@ -223,7 +223,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせ', 0, 3, null, null), 'せんせい', - 0, 4, true, false, + 0, 4, true, 'せんせい', 3 ); @@ -233,7 +233,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせい', 0, 4, null, null), 'せんせい', - 4, 4, true, false, + 4, 4, true, '', 0 ); }); @@ -252,7 +252,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせい', 0, 4, null, null), 'せんせい', - 0, 4, true, false, + 0, 4, true, 'せんせい', 4 ); @@ -262,7 +262,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせい', 0, 4, null, null), '先生', - 0, 2, true, false, + 0, 2, true, '先生', 4 ); @@ -272,7 +272,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('先生', 0, 2, null, null), '先生', - 2, 2, true, false, + 2, 2, true, '', 0 ); }); @@ -281,7 +281,7 @@ suite('TextAreaState', () => { testDeduceInput( null, 'a', - 0, 1, true, false, + 0, 1, true, 'a', 0 ); }); @@ -290,7 +290,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState(']\n', 1, 2, null, null), ']\n', - 2, 2, true, false, + 2, 2, true, '\n', 0 ); }); @@ -299,7 +299,7 @@ suite('TextAreaState', () => { testDeduceInput( null, 'a', - 1, 1, true, false, + 1, 1, true, 'a', 0 ); }); @@ -308,7 +308,7 @@ suite('TextAreaState', () => { testDeduceInput( TextAreaState.EMPTY, 'a', - 0, 1, true, false, + 0, 1, true, 'a', 0 ); }); @@ -317,7 +317,7 @@ suite('TextAreaState', () => { testDeduceInput( TextAreaState.EMPTY, 'a', - 1, 1, true, false, + 1, 1, true, 'a', 0 ); }); @@ -326,7 +326,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 0, 12, null, null), 'H', - 1, 1, true, false, + 1, 1, true, 'H', 0 ); }); @@ -335,7 +335,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 12, 12, null, null), 'Hello world!a', - 13, 13, true, false, + 13, 13, true, 'a', 0 ); }); @@ -344,7 +344,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 0, 0, null, null), 'aHello world!', - 1, 1, true, false, + 1, 1, true, 'a', 0 ); }); @@ -353,7 +353,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 6, 11, null, null), 'Hello other!', - 11, 11, true, false, + 11, 11, true, 'other', 0 ); }); @@ -362,7 +362,7 @@ suite('TextAreaState', () => { testDeduceInput( TextAreaState.EMPTY, 'これは', - 3, 3, true, false, + 3, 3, true, 'これは', 0 ); }); @@ -371,7 +371,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 0, 0, null, null), 'Aello world!', - 1, 1, true, false, + 1, 1, true, 'A', 0 ); }); @@ -380,7 +380,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 5, 5, null, null), 'Hellö world!', - 4, 5, true, false, + 4, 5, true, 'ö', 0 ); }); @@ -389,7 +389,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 5, 5, null, null), 'Hellöö world!', - 5, 5, true, false, + 5, 5, true, 'öö', 1 ); }); @@ -398,7 +398,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 5, 5, null, null), 'Helöö world!', - 5, 5, true, false, + 5, 5, true, 'öö', 2 ); }); @@ -407,7 +407,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 5, 5, null, null), 'Hellö world!', - 5, 5, true, false, + 5, 5, true, 'ö', 1 ); }); @@ -416,7 +416,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('a', 0, 1, null, null), 'a', - 1, 1, true, false, + 1, 1, true, 'a', 0 ); }); @@ -425,7 +425,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('x x', 0, 1, null, null), 'x x', - 1, 1, true, false, + 1, 1, true, 'x', 0 ); }); @@ -455,7 +455,7 @@ suite('TextAreaState', () => { 'some6 text', 'some7 text' ].join('\n'), - 4, 4, true, false, + 4, 4, true, '📅', 0 ); }); @@ -469,7 +469,7 @@ suite('TextAreaState', () => { null, null ), 'some💊1 text', - 6, 6, true, false, + 6, 6, true, '💊', 0 ); }); @@ -483,7 +483,7 @@ suite('TextAreaState', () => { null, null ), 'qwertyu\nasdfghj\nzxcvbnm🎈', - 25, 25, true, false, + 25, 25, true, '🎈', 0 ); }); @@ -498,39 +498,11 @@ suite('TextAreaState', () => { null, null ), 'some⌨️1 text', - 6, 6, true, false, + 6, 6, true, '⌨️', 0 ); }); - test('issue #42251: Minor issue, character swapped when typing', () => { - // Typing on OSX occurs at offset 0 after moving the window using the custom (non-native) titlebar. - testDeduceInput( - new TextAreaState( - 'ab', - 2, 2, - null, null - ), - 'cab', - 1, 1, true, true, - 'c', 0 - ); - }); - - test('issue #49480: Double curly braces inserted', () => { - // Characters get doubled - testDeduceInput( - new TextAreaState( - 'aa', - 2, 2, - null, null - ), - 'aaa', - 3, 3, true, true, - 'a', 0 - ); - }); - suite('PagedScreenReaderStrategy', () => { function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void { diff --git a/src/vs/editor/test/common/mocks/testConfiguration.ts b/src/vs/editor/test/common/mocks/testConfiguration.ts index 89bd002a50..ff4ec81f98 100644 --- a/src/vs/editor/test/common/mocks/testConfiguration.ts +++ b/src/vs/editor/test/common/mocks/testConfiguration.ts @@ -42,6 +42,7 @@ export class TestConfiguration extends CommonEditorConfiguration { canUseHalfwidthRightwardsArrow: true, spaceWidth: 10, middotWidth: 10, + wsmiddotWidth: 10, maxDigitWidth: 10, }, true); } diff --git a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts index 471b7853a0..354b131f3b 100644 --- a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts +++ b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts @@ -28,7 +28,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout 1', () => { // Start off with 10 lines - let linesLayout = new LinesLayout(10, 10); + let linesLayout = new LinesLayout(10, 10, 0, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: - @@ -137,7 +137,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout 2', () => { // Start off with 10 lines and one whitespace after line 2, of height 5 - let linesLayout = new LinesLayout(10, 1); + let linesLayout = new LinesLayout(10, 1, 0, 0); let a = insertWhitespace(linesLayout, 2, 0, 5, 0); // 10 lines @@ -232,8 +232,103 @@ suite('Editor ViewLayout - LinesLayout', () => { assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 9); }); + test('LinesLayout Padding', () => { + // Start off with 10 lines + let linesLayout = new LinesLayout(10, 10, 15, 20); + + // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + // whitespace: - + assert.equal(linesLayout.getLinesTotalHeight(), 135); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 15); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 25); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 35); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 45); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 55); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 65); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(7), 75); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(8), 85); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(9), 95); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(10), 105); + + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(24), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(25), 2); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(34), 2); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); + + // Add whitespace of height 5px after 2nd line + insertWhitespace(linesLayout, 2, 0, 5, 0); + // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + // whitespace: a(2,5) + assert.equal(linesLayout.getLinesTotalHeight(), 140); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 15); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 25); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 40); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 50); + + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(10), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(25), 2); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(34), 2); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(39), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(40), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(41), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(49), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(50), 4); + + // Add two more whitespaces of height 5px + insertWhitespace(linesLayout, 3, 0, 5, 0); + insertWhitespace(linesLayout, 4, 0, 5, 0); + // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + // whitespace: a(2,5), b(3, 5), c(4, 5) + assert.equal(linesLayout.getLinesTotalHeight(), 150); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 15); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 25); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(3), 40); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(4), 55); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(5), 70); + assert.equal(linesLayout.getVerticalOffsetForLineNumber(6), 80); + + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(0), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(15), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(24), 1); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(30), 2); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(35), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(39), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(40), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(49), 3); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(50), 4); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(54), 4); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(55), 4); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(64), 4); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(65), 5); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(69), 5); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(70), 5); + assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(80), 6); + + assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(0), 35); // 35 -> 40 + assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(1), 50); // 50 -> 55 + assert.equal(linesLayout.getVerticalOffsetForWhitespaceIndex(2), 65); + + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(0), 0); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(34), 0); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(35), 0); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(39), 0); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(40), 1); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(49), 1); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(50), 1); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(54), 1); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(55), 2); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(64), 2); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(65), 2); + assert.equal(linesLayout.getWhitespaceIndexAtOrAfterVerticallOffset(70), -1); + }); + test('LinesLayout getLineNumberAtOrAfterVerticalOffset', () => { - let linesLayout = new LinesLayout(10, 1); + let linesLayout = new LinesLayout(10, 1, 0, 0); insertWhitespace(linesLayout, 6, 0, 10, 0); // 10 lines @@ -282,7 +377,7 @@ suite('Editor ViewLayout - LinesLayout', () => { }); test('LinesLayout getCenteredLineInViewport', () => { - let linesLayout = new LinesLayout(10, 1); + let linesLayout = new LinesLayout(10, 1, 0, 0); insertWhitespace(linesLayout, 6, 0, 10, 0); // 10 lines @@ -365,7 +460,7 @@ suite('Editor ViewLayout - LinesLayout', () => { }); test('LinesLayout getLinesViewportData 1', () => { - let linesLayout = new LinesLayout(10, 10); + let linesLayout = new LinesLayout(10, 10, 0, 0); insertWhitespace(linesLayout, 6, 0, 100, 0); // 10 lines @@ -498,7 +593,7 @@ suite('Editor ViewLayout - LinesLayout', () => { }); test('LinesLayout getLinesViewportData 2 & getWhitespaceViewportData', () => { - let linesLayout = new LinesLayout(10, 10); + let linesLayout = new LinesLayout(10, 10, 0, 0); let a = insertWhitespace(linesLayout, 6, 0, 100, 0); let b = insertWhitespace(linesLayout, 7, 0, 50, 0); @@ -569,7 +664,7 @@ suite('Editor ViewLayout - LinesLayout', () => { }); test('LinesLayout getWhitespaceAtVerticalOffset', () => { - let linesLayout = new LinesLayout(10, 10); + let linesLayout = new LinesLayout(10, 10, 0, 0); let a = insertWhitespace(linesLayout, 6, 0, 100, 0); let b = insertWhitespace(linesLayout, 7, 0, 50, 0); @@ -612,7 +707,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout', () => { - const linesLayout = new LinesLayout(100, 20); + const linesLayout = new LinesLayout(100, 20, 0, 0); // Insert a whitespace after line number 2, of height 10 const a = insertWhitespace(linesLayout, 2, 0, 10, 0); @@ -963,7 +1058,7 @@ suite('Editor ViewLayout - LinesLayout', () => { }); test('LinesLayout changeWhitespaceAfterLineNumber & getFirstWhitespaceIndexAfterLineNumber', () => { - const linesLayout = new LinesLayout(100, 20); + const linesLayout = new LinesLayout(100, 20, 0, 0); const a = insertWhitespace(linesLayout, 0, 0, 1, 0); const b = insertWhitespace(linesLayout, 7, 0, 1, 0); @@ -1087,7 +1182,7 @@ suite('Editor ViewLayout - LinesLayout', () => { }); test('LinesLayout Bug', () => { - const linesLayout = new LinesLayout(100, 20); + const linesLayout = new LinesLayout(100, 20, 0, 0); const a = insertWhitespace(linesLayout, 0, 0, 1, 0); const b = insertWhitespace(linesLayout, 7, 0, 1, 0); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 60e42e3ee6..a74d1c8566 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -40,6 +40,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 0, 0, + 0, -1, 'none', false, @@ -92,6 +93,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 0, 0, + 0, -1, 'none', false, @@ -147,6 +149,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, 6, 'boundary', false, @@ -241,6 +244,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'boundary', false, @@ -306,6 +310,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -371,6 +376,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -413,6 +419,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -446,6 +453,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -549,6 +557,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -590,6 +599,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -622,6 +632,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -671,6 +682,7 @@ suite('viewLineRenderer.renderLine', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -755,6 +767,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, -1, renderWhitespace, false, @@ -783,6 +796,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -825,6 +839,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -1242,6 +1257,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, -1, 'none', false, @@ -1285,6 +1301,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, -1, 'all', false, @@ -1320,6 +1337,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, -1, 'all', false, @@ -1356,6 +1374,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, -1, 'all', false, @@ -1388,6 +1407,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1424,6 +1444,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1460,6 +1481,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1493,6 +1515,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1525,6 +1548,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'all', false, @@ -1563,6 +1587,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1595,6 +1620,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1629,6 +1655,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1662,6 +1689,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'boundary', false, @@ -1693,6 +1721,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1728,6 +1757,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, 10000, 'none', false, @@ -1759,6 +1789,7 @@ suite('viewLineRenderer.renderLine 2', () => { 0, 10, 10, + 10, -1, 'none', false, diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index 477b1c782a..db65ff8839 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -58,6 +58,7 @@ function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number, canUseHalfwidthRightwardsArrow: true, spaceWidth: 7, middotWidth: 7, + wsmiddotWidth: 7, maxDigitWidth: 7 }, false); const lineBreaksComputer = factory.createLineBreaksComputer(fontInfo, tabSize, breakAfter, wrappingIndent); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 909898e7bc..6aeac0f5b2 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1067,7 +1067,7 @@ declare namespace monaco.editor { tabSize?: number; /** * Insert spaces when pressing `Tab`. - * This setting is overridden based on the file contents when detectIndentation` is on. + * This setting is overridden based on the file contents when `detectIndentation` is on. * Defaults to true. */ insertSpaces?: boolean; @@ -1184,7 +1184,7 @@ declare namespace monaco.editor { severity: MarkerSeverity; code?: string | { value: string; - link: Uri; + target: Uri; }; message: string; source?: string; @@ -1202,7 +1202,7 @@ declare namespace monaco.editor { export interface IMarkerData { code?: string | { value: string; - link: Uri; + target: Uri; }; severity: MarkerSeverity; message: string; @@ -2841,6 +2841,10 @@ declare namespace monaco.editor { * Defaults to 10 (ms) */ quickSuggestionsDelay?: number; + /** + * Controls the spacing around the editor. + */ + padding?: IEditorPaddingOptions; /** * Parameter hint options. */ @@ -3395,6 +3399,25 @@ declare namespace monaco.editor { export type EditorMinimapOptions = Readonly>; + /** + * Configuration options for editor padding + */ + export interface IEditorPaddingOptions { + /** + * Spacing between top edge of editor and first line. + */ + top?: number; + /** + * Spacing between bottom edge of editor and last line. + */ + bottom?: number; + } + + export interface InternalEditorPaddingOptions { + readonly top: number; + readonly bottom: number; + } + /** * Configuration options for parameter hints */ @@ -3763,53 +3786,54 @@ declare namespace monaco.editor { occurrencesHighlight = 61, overviewRulerBorder = 62, overviewRulerLanes = 63, - parameterHints = 64, - peekWidgetDefaultFocus = 65, - definitionLinkOpensInPeek = 66, - quickSuggestions = 67, - quickSuggestionsDelay = 68, - readOnly = 69, - renderControlCharacters = 70, - renderIndentGuides = 71, - renderFinalNewline = 72, - renderLineHighlight = 73, - renderValidationDecorations = 74, - renderWhitespace = 75, - revealHorizontalRightPadding = 76, - roundedSelection = 77, - rulers = 78, - scrollbar = 79, - scrollBeyondLastColumn = 80, - scrollBeyondLastLine = 81, - scrollPredominantAxis = 82, - selectionClipboard = 83, - selectionHighlight = 84, - selectOnLineNumbers = 85, - showFoldingControls = 86, - showUnused = 87, - snippetSuggestions = 88, - smoothScrolling = 89, - stopRenderingLineAfter = 90, - suggest = 91, - suggestFontSize = 92, - suggestLineHeight = 93, - suggestOnTriggerCharacters = 94, - suggestSelection = 95, - tabCompletion = 96, - useTabStops = 97, - wordSeparators = 98, - wordWrap = 99, - wordWrapBreakAfterCharacters = 100, - wordWrapBreakBeforeCharacters = 101, - wordWrapColumn = 102, - wordWrapMinified = 103, - wrappingIndent = 104, - wrappingStrategy = 105, - editorClassName = 106, - pixelRatio = 107, - tabFocusMode = 108, - layoutInfo = 109, - wrappingInfo = 110 + padding = 64, + parameterHints = 65, + peekWidgetDefaultFocus = 66, + definitionLinkOpensInPeek = 67, + quickSuggestions = 68, + quickSuggestionsDelay = 69, + readOnly = 70, + renderControlCharacters = 71, + renderIndentGuides = 72, + renderFinalNewline = 73, + renderLineHighlight = 74, + renderValidationDecorations = 75, + renderWhitespace = 76, + revealHorizontalRightPadding = 77, + roundedSelection = 78, + rulers = 79, + scrollbar = 80, + scrollBeyondLastColumn = 81, + scrollBeyondLastLine = 82, + scrollPredominantAxis = 83, + selectionClipboard = 84, + selectionHighlight = 85, + selectOnLineNumbers = 86, + showFoldingControls = 87, + showUnused = 88, + snippetSuggestions = 89, + smoothScrolling = 90, + stopRenderingLineAfter = 91, + suggest = 92, + suggestFontSize = 93, + suggestLineHeight = 94, + suggestOnTriggerCharacters = 95, + suggestSelection = 96, + tabCompletion = 97, + useTabStops = 98, + wordSeparators = 99, + wordWrap = 100, + wordWrapBreakAfterCharacters = 101, + wordWrapBreakBeforeCharacters = 102, + wordWrapColumn = 103, + wordWrapMinified = 104, + wrappingIndent = 105, + wrappingStrategy = 106, + editorClassName = 107, + pixelRatio = 108, + tabFocusMode = 109, + layoutInfo = 110, + wrappingInfo = 111 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -3876,6 +3900,7 @@ declare namespace monaco.editor { occurrencesHighlight: IEditorOption; overviewRulerBorder: IEditorOption; overviewRulerLanes: IEditorOption; + padding: IEditorOption; parameterHints: IEditorOption; peekWidgetDefaultFocus: IEditorOption; definitionLinkOpensInPeek: IEditorOption; @@ -4669,6 +4694,7 @@ declare namespace monaco.editor { readonly canUseHalfwidthRightwardsArrow: boolean; readonly spaceWidth: number; readonly middotWidth: number; + readonly wsmiddotWidth: number; readonly maxDigitWidth: number; } diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index 893f6d7456..b52e39a360 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -16,6 +16,7 @@ export interface IAccessibilityService { alwaysUnderlineAccessKeys(): Promise; isScreenReaderOptimized(): boolean; + getAccessibilitySupport(): AccessibilitySupport; setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void; } diff --git a/src/vs/platform/accessibility/common/accessibilityService.ts b/src/vs/platform/accessibility/common/accessibilityService.ts index 43731615ce..802bb6992a 100644 --- a/src/vs/platform/accessibility/common/accessibilityService.ts +++ b/src/vs/platform/accessibility/common/accessibilityService.ts @@ -42,6 +42,10 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe return config === 'on' || (config === 'auto' && this._accessibilitySupport === AccessibilitySupport.Enabled); } + getAccessibilitySupport(): AccessibilitySupport { + return this._accessibilitySupport; + } + alwaysUnderlineAccessKeys(): Promise { return Promise.resolve(false); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 19fe24c222..24b5832458 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -324,10 +324,10 @@ export class SyncActionDescriptor { public static create(ctor: { new(id: string, label: string, ...services: Services): Action }, id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number ): SyncActionDescriptor { - return new SyncActionDescriptor(ctor as IConstructorSignature2, id, label, keybindings, keybindingContext, keybindingWeight); + return new SyncActionDescriptor(ctor as IConstructorSignature2, id, label, keybindings, keybindingContext, keybindingWeight); } - private constructor(ctor: IConstructorSignature2, + private constructor(ctor: IConstructorSignature2, id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number ) { this._id = id; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 4c95d6b9d9..afdcd4ea14 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -223,8 +223,6 @@ export interface IGlobalExtensionEnablementService { enableExtension(extension: IExtensionIdentifier, source?: string): Promise; disableExtension(extension: IExtensionIdentifier, source?: string): Promise; - // Async method until storage service is available in shared process - getDisabledExtensionsAsync(): Promise; } export const ExtensionsLabel = localize('extensions', "Extensions"); diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 60d95941cc..64d6043fcd 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; @@ -130,53 +130,3 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer return Promise.resolve(this.channel.call('getExtensionsReport')); } } - -export class GlobalExtensionEnablementServiceChannel implements IServerChannel { - - constructor(private readonly service: IGlobalExtensionEnablementService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChangeEnablement': return this.service.onDidChangeEnablement; - } - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case 'getDisabledExtensionsAsync': return Promise.resolve(this.service.getDisabledExtensions()); - case 'enableExtension': return this.service.enableExtension(args[0]); - case 'disableExtension': return this.service.disableExtension(args[0]); - } - throw new Error('Invalid call'); - } -} - -export class GlobalExtensionEnablementServiceClient implements IGlobalExtensionEnablementService { - - _serviceBrand: undefined; - - get onDidChangeEnablement(): Event<{ readonly extensions: IExtensionIdentifier[], readonly source?: string }> { return this.channel.listen('onDidChangeEnablement'); } - - constructor(private readonly channel: IChannel) { - } - - getDisabledExtensionsAsync(): Promise { - return this.channel.call('getDisabledExtensionsAsync'); - } - - enableExtension(extension: IExtensionIdentifier): Promise { - return this.channel.call('enableExtension', [extension]); - } - - disableExtension(extension: IExtensionIdentifier): Promise { - return this.channel.call('disableExtension', [extension]); - } - - getDisabledExtensions(): IExtensionIdentifier[] { - throw new Error('not supported'); - } - -} - - diff --git a/src/vs/platform/instantiation/common/extensions.ts b/src/vs/platform/instantiation/common/extensions.ts index 2c5bbe9646..ed9702cebf 100644 --- a/src/vs/platform/instantiation/common/extensions.ts +++ b/src/vs/platform/instantiation/common/extensions.ts @@ -8,8 +8,8 @@ import { ServiceIdentifier, BrandedService } from './instantiation'; const _registry: [ServiceIdentifier, SyncDescriptor][] = []; -export function registerSingleton(id: ServiceIdentifier, ctor: { new(...services: Services): T }, supportsDelayedInstantiation?: boolean): void { - _registry.push([id, new SyncDescriptor(ctor, [], supportsDelayedInstantiation)]); +export function registerSingleton(id: ServiceIdentifier, ctor: new (...services: Services) => T, supportsDelayedInstantiation?: boolean): void { + _registry.push([id, new SyncDescriptor(ctor as new (...args: any[]) => T, [], supportsDelayedInstantiation)]); } export function getSingletonServiceDescriptors(): [ServiceIdentifier, SyncDescriptor][] { diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 9ae950c86f..3ac1450c35 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -64,7 +64,7 @@ export class InstantiationService implements IInstantiationService { return result; } }; - return fn.apply(undefined, [accessor, ...args]); + return fn(accessor, ...args); } finally { _done = true; _trace.stop(); diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 0060e4afac..36835eb840 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -87,7 +87,7 @@ export namespace MarkerSeverity { * A structure defining a problem/warning/etc. */ export interface IMarkerData { - code?: string | { value: string; link: URI }; + code?: string | { value: string; target: URI }; severity: MarkerSeverity; message: string; source?: string; @@ -108,7 +108,7 @@ export interface IMarker { owner: string; resource: URI; severity: MarkerSeverity; - code?: string | { value: string; link: URI }; + code?: string | { value: string; target: URI }; message: string; source?: string; startLineNumber: number; diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 711c7fb170..0d1def31fd 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -284,14 +284,12 @@ export class Menubar { this.setMenuById(editMenu, 'Edit'); menubar.append(editMenuItem); - // {{SQL CARBON EDIT}} - Disable unused menus - // // Selection - // const selectionMenu = new Menu(); - // const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu }); + // Selection + /*const selectionMenu = new Menu(); + const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu }); - // this.setMenuById(selectionMenu, 'Selection'); - // menubar.append(selectionMenuItem); - // {{SQL CARBON EDIT}} - End + this.setMenuById(selectionMenu, 'Selection'); + menubar.append(selectionMenuItem); {{SQL CARBON EDIT}} - Disable unused menus */ // View const viewMenu = new Menu(); @@ -300,32 +298,26 @@ export class Menubar { this.setMenuById(viewMenu, 'View'); menubar.append(viewMenuItem); - // {{SQL CARBON EDIT}} - Disable unused menus - // // Go - // const gotoMenu = new Menu(); - // const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu }); + // Go + /* const gotoMenu = new Menu(); + const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu }); - // this.setMenuById(gotoMenu, 'Go'); - // menubar.append(gotoMenuItem); - // {{SQL CARBON EDIT}} - End + this.setMenuById(gotoMenu, 'Go'); + menubar.append(gotoMenuItem); {{SQL CARBON EDIT}} - Disable unused menus */ - // {{SQL CARBON EDIT}} - Disable unused menus - // // Debug - // const debugMenu = new Menu(); - // const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu }); + // Debug + /*const debugMenu = new Menu(); + const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run")), submenu: debugMenu }); - // this.setMenuById(debugMenu, 'Debug'); - // menubar.append(debugMenuItem); - // {{SQL CARBON EDIT}} - End + this.setMenuById(debugMenu, 'Run'); + menubar.append(debugMenuItem); {{SQL CARBON EDIT}} - Disable unused menus */ - // {{SQL CARBON EDIT}} - Disable unused menus - // // Terminal - // const terminalMenu = new Menu(); - // const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu }); + // Terminal + /*const terminalMenu = new Menu(); + const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu }); - // this.setMenuById(terminalMenu, 'Terminal'); - // menubar.append(terminalMenuItem); - // {{SQL CARBON EDIT}} - End + this.setMenuById(terminalMenu, 'Terminal'); + menubar.append(terminalMenuItem); {{SQL CARBON EDIT}} - Disable unused menus */ // Mac: Window let macWindowMenuItem: MenuItem | undefined; diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index b6f9ed8e96..33517db849 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -15,7 +15,7 @@ import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; export class NativeStorageService extends Disposable implements IStorageService { @@ -238,14 +238,11 @@ export class NativeStorageService extends Disposable implements IStorageService } async logStorage(): Promise { - const [workspaceStorage, workspaceStoragePath] = assertAllDefined(this.workspaceStorage, this.workspaceStoragePath); - - const result = await Promise.all([ + return logStorage( this.globalStorage.items, - workspaceStorage.items - ]); - - logStorage(result[0], result[1], this.environmentService.globalStorageHome, workspaceStoragePath); + this.workspaceStorage ? this.workspaceStorage.items : new Map(), // Shared process storage does not has workspace storage + this.environmentService.globalStorageHome, + this.workspaceStoragePath || ''); } async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index bd656deaeb..0f91a88871 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -401,22 +401,22 @@ function registerDefaultClassifications(): void { registerTokenType('namespace', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); - registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['support.type'], ['support.class']]); - registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']], 'type'); - registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.type.class']], 'type'); - registerTokenType('interface', nls.localize('interface', "Style for interfaces."), [['entity.name.type.interface']], 'type'); - registerTokenType('enum', nls.localize('enum', "Style for enums."), [['entity.name.type.enum']], 'type'); - registerTokenType('typeParameter', nls.localize('typeParameter', "Style for type parameters."), [['entity.name.type', 'meta.type.parameters']], 'type'); + registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['support.type']]); + registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']]); + registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.type.class'], ['support.class']]); + registerTokenType('interface', nls.localize('interface', "Style for interfaces."), [['entity.name.type.interface']]); + registerTokenType('enum', nls.localize('enum', "Style for enums."), [['entity.name.type.enum']]); + registerTokenType('typeParameter', nls.localize('typeParameter', "Style for type parameters."), [['entity.name.type.parameter']]); registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); registerTokenType('member', nls.localize('member', "Style for member"), [['entity.name.function.member'], ['support.function']]); - registerTokenType('macro', nls.localize('macro', "Style for macros."), [['entity.name.other.preprocessor.macro']], 'function'); + registerTokenType('macro', nls.localize('macro', "Style for macros."), [['entity.name.other.preprocessor.macro']]); registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable.other.readwrite'], ['entity.name.variable']]); - registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), [['variable.parameter']], 'variable'); - registerTokenType('property', nls.localize('property', "Style for properties."), [['variable.other.property']], 'variable'); - registerTokenType('enumMember', nls.localize('enumMember', "Style for enum members."), [['variable.other.enummember']], 'variable'); - registerTokenType('event', nls.localize('event', "Style for events."), [['variable.other.event']], 'variable'); + registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), [['variable.parameter']]); + registerTokenType('property', nls.localize('property', "Style for properties."), [['variable.other.property']]); + registerTokenType('enumMember', nls.localize('enumMember', "Style for enum members."), [['variable.other.enummember']]); + registerTokenType('event', nls.localize('event', "Style for events."), [['variable.other.event']]); registerTokenType('label', nls.localize('labels', "Style for labels. "), undefined); diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index f8cca34e60..38dd9e5766 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -26,7 +26,7 @@ export abstract class AbstractUpdateService implements IUpdateService { _serviceBrand: undefined; - protected readonly url: string | undefined; + protected url: string | undefined; private _state: State = State.Uninitialized; @@ -49,7 +49,14 @@ export abstract class AbstractUpdateService implements IUpdateService { @IEnvironmentService private readonly environmentService: IEnvironmentService, @IRequestService protected requestService: IRequestService, @ILogService protected logService: ILogService, - ) { + ) { } + + /** + * This must be called before any other call. This is a performance + * optimization, to avoid using extra CPU cycles before first window open. + * https://github.com/microsoft/vscode/issues/89784 + */ + initialize(): void { if (!this.environmentService.isBuilt) { return; // updates are never enabled when running out of sources } @@ -173,6 +180,7 @@ export abstract class AbstractUpdateService implements IUpdateService { if (!this.url) { return Promise.resolve(undefined); } + return this.requestService.request({ url: this.url }, CancellationToken.None).then(context => { // The update server replies with 204 (No Content) when no // update is available - that's all we want to know. diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index c3ee032fef..41a56d79fb 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -36,6 +36,10 @@ export class DarwinUpdateService extends AbstractUpdateService { @ILogService logService: ILogService ) { super(lifecycleMainService, configurationService, environmentService, requestService, logService); + } + + initialize(): void { + super.initialize(); this.onRawError(this.onError, this, this.disposables); this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index ab1e95ebe3..98113d6f9c 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -70,6 +70,10 @@ export class Win32UpdateService extends AbstractUpdateService { @IFileService private readonly fileService: IFileService ) { super(lifecycleMainService, configurationService, environmentService, requestService, logService); + } + + initialize(): void { + super.initialize(); if (getUpdateType() === UpdateType.Setup) { /* __GDPR__ @@ -82,7 +86,7 @@ export class Win32UpdateService extends AbstractUpdateService { "target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - telemetryService.publicLog('update:win32SetupTarget', { target: product.target }); + this.telemetryService.publicLog('update:win32SetupTarget', { target: product.target }); } } diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 19db80ca22..bf96494742 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -58,7 +58,7 @@ export class ElectronURLListener { return url; }); - const onOpenUrl = Event.filter(Event.map(onOpenElectronUrl, uriFromRawUrl), uri => !!uri); + const onOpenUrl = Event.filter(Event.map(onOpenElectronUrl, uriFromRawUrl), (uri): uri is URI => !!uri); onOpenUrl(this.urlService.open, this.urlService, this.disposables); const isWindowReady = windowsMainService.getWindows() diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index cad313cdfb..501bfad9ee 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -4,15 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { IFileService, IFileContent } from 'vs/platform/files/common/files'; +import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { joinPath, dirname } from 'vs/base/common/resources'; import { toLocalISOString } from 'vs/base/common/date'; -import { ThrottledDelayer } from 'vs/base/common/async'; +import { ThrottledDelayer, CancelablePromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ParseError, parse } from 'vs/base/common/json'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; + +type SyncConflictsClassification = { + source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; export abstract class AbstractSynchroniser extends Disposable { @@ -34,6 +41,9 @@ export abstract class AbstractSynchroniser extends Disposable { @IFileService protected readonly fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, ) { super(); this.syncFolder = joinPath(environmentService.userDataSyncHome, source); @@ -43,20 +53,53 @@ export abstract class AbstractSynchroniser extends Disposable { protected setStatus(status: SyncStatus): void { if (this._status !== status) { + const oldStatus = this._status; this._status = status; this._onDidChangStatus.fire(status); + if (status === SyncStatus.HasConflicts) { + // Log to telemetry when there is a sync conflict + this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsDetected', { source: this.source }); + } + if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { + // Log to telemetry when conflicts are resolved + this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsResolved', { source: this.source }); + } } } + protected get enabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resourceKey); } + + async sync(ref?: string): Promise { + if (!this.enabled) { + this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is disabled.`); + return; + } + if (this.status === SyncStatus.HasConflicts) { + this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as there are conflicts.`); + return; + } + if (this.status === SyncStatus.Syncing) { + this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is running already.`); + return; + } + + this.logService.trace(`${this.source}: Started synchronizing ${this.source.toLowerCase()}...`); + this.setStatus(SyncStatus.Syncing); + + const lastSyncUserData = await this.getLastSyncUserData(); + const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData); + return this.doSync(remoteUserData, lastSyncUserData); + } + async hasPreviouslySynced(): Promise { const lastSyncData = await this.getLastSyncUserData(); return !!lastSyncData; } - async hasRemoteData(): Promise { + async getRemoteContent(): Promise { const lastSyncData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncData); - return remoteUserData.content !== null; + return remoteUserData.content; } async resetLocal(): Promise { @@ -79,11 +122,11 @@ export abstract class AbstractSynchroniser extends Disposable { } protected async getRemoteUserData(lastSyncData: IUserData | null): Promise { - return this.userDataSyncStoreService.read(this.getRemoteDataResourceKey(), lastSyncData, this.source); + return this.userDataSyncStoreService.read(this.resourceKey, lastSyncData, this.source); } protected async updateRemoteUserData(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(this.getRemoteDataResourceKey(), content, ref, this.source); + return this.userDataSyncStoreService.write(this.resourceKey, content, ref, this.source); } protected async backupLocal(content: VSBuffer): Promise { @@ -101,21 +144,56 @@ export abstract class AbstractSynchroniser extends Disposable { } } - protected abstract getRemoteDataResourceKey(): string; + abstract readonly resourceKey: ResourceKey; + protected abstract doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise; +} + +export interface IFileSyncPreviewResult { + readonly fileContent: IFileContent | null; + readonly remoteUserData: IUserData; + readonly lastSyncUserData: IUserData | null; + readonly content: string | null; + readonly hasLocalChanged: boolean; + readonly hasRemoteChanged: boolean; + readonly hasConflicts: boolean; } export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { + protected syncPreviewResultPromise: CancelablePromise | null = null; + constructor( protected readonly file: URI, - readonly source: SyncSource, + source: SyncSource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, + @ITelemetryService telemetryService: ITelemetryService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, ) { - super(source, fileService, environmentService, userDataSyncStoreService); + super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService); this._register(this.fileService.watch(dirname(file))); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(file))(() => this._onDidChangeLocal.fire())); + this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); + } + + async stop(): Promise { + this.cancel(); + this.logService.trace(`${this.source}: Stopped synchronizing ${this.source.toLowerCase()}.`); + try { + await this.fileService.del(this.conflictsPreviewResource); + } catch (e) { /* ignore */ } + this.setStatus(SyncStatus.Idle); + } + + async getRemoteContent(preview?: boolean): Promise { + if (preview) { + if (this.syncPreviewResultPromise) { + const result = await this.syncPreviewResultPromise; + return result.remoteUserData ? result.remoteUserData.content : null; + } + } + return super.getRemoteContent(); } protected async getLocalFileContent(): Promise { @@ -127,14 +205,87 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { } protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null): Promise { - if (oldContent) { - // file exists already - await this.backupLocal(oldContent.value); - await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent); - } else { - // file does not exist - await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false }); + try { + if (oldContent) { + // file exists already + await this.backupLocal(oldContent.value); + await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent); + } else { + // file does not exist + await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false }); + } + } catch (e) { + if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || + (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { + throw new UserDataSyncError(e.message, UserDataSyncErrorCode.LocalPreconditionFailed); + } else { + throw e; + } } } + private onFileChanges(e: FileChangesEvent): void { + if (!e.contains(this.file)) { + return; + } + + if (!this.enabled) { + return; + } + + // Sync again if local file has changed and current status is in conflicts + if (this.status === SyncStatus.HasConflicts) { + this.syncPreviewResultPromise?.then(result => { + this.cancel(); + this.doSync(result.remoteUserData, result.lastSyncUserData); + }); + } + + // Otherwise fire change event + else { + this._onDidChangeLocal.fire(); + } + + } + + protected cancel(): void { + if (this.syncPreviewResultPromise) { + this.syncPreviewResultPromise.cancel(); + this.syncPreviewResultPromise = null; + } + } + + protected abstract readonly conflictsPreviewResource: URI; +} + +export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser { + + constructor( + file: URI, + source: SyncSource, + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, + @ITelemetryService telemetryService: ITelemetryService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, + ) { + super(file, source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService); + } + + protected hasErrors(content: string): boolean { + const parseErrors: ParseError[] = []; + parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); + return parseErrors.length > 0; + } + + private _formattingOptions: Promise | undefined = undefined; + protected getFormattingOptions(): Promise { + if (!this._formattingOptions) { + this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.file); + } + return this._formattingOptions; + } + } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 17d97b33d4..4878e727d8 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -15,6 +15,7 @@ import { localize } from 'vs/nls'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; interface ISyncPreviewResult { readonly added: ISyncExtension[]; @@ -32,17 +33,21 @@ interface ILastSyncUserData extends IUserData { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { + readonly resourceKey: ResourceKey = 'extensions'; + constructor( @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService); + super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService); this._register( Event.debounce( Event.any( @@ -52,10 +57,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse () => undefined, 500)(() => this._onDidChangeLocal.fire())); } - protected getRemoteDataResourceKey(): string { return 'extensions'; } - async pull(): Promise { - if (!this.configurationService.getValue('sync.enableExtensions')) { + if (!this.enabled) { this.logService.info('Extensions: Skipped pulling extensions as it is disabled.'); return; } @@ -88,7 +91,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } async push(): Promise { - if (!this.configurationService.getValue('sync.enableExtensions')) { + if (!this.enabled) { this.logService.info('Extensions: Skipped pushing extensions as it is disabled.'); return; } @@ -112,46 +115,16 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } - async sync(): Promise { - if (!this.configurationService.getValue('sync.enableExtensions')) { - this.logService.trace('Extensions: Skipping synchronizing extensions as it is disabled.'); - return; - } + async sync(ref?: string): Promise { if (!this.extensionGalleryService.isEnabled()) { - this.logService.trace('Extensions: Skipping synchronizing extensions as gallery is disabled.'); + this.logService.info('Extensions: Skipping synchronizing extensions as gallery is disabled.'); return; } - if (this.status !== SyncStatus.Idle) { - this.logService.trace('Extensions: Skipping synchronizing extensions as it is running already.'); - return; - } - - this.logService.trace('Extensions: Started synchronizing extensions...'); - this.setStatus(SyncStatus.Syncing); - - try { - const previewResult = await this.getPreview(); - await this.apply(previewResult); - } catch (e) { - this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Extensions: Failed to synchronize extensions as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - throw e; - } - - this.logService.trace('Extensions: Finished synchronizing extensions.'); - this.setStatus(SyncStatus.Idle); + return super.sync(ref); } async stop(): Promise { } - async restart(): Promise { - throw new Error('Extensions: Conflicts should not occur'); - } - accept(content: string): Promise { throw new Error('Extensions: Conflicts should not occur'); } @@ -172,14 +145,29 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return null; } - private async getPreview(): Promise { - const lastSyncUserData = await this.getLastSyncUserData(); + protected async doSync(remoteUserData: IUserData, lastSyncUserData: ILastSyncUserData | null): Promise { + try { + const previewResult = await this.getPreview(remoteUserData, lastSyncUserData); + await this.apply(previewResult); + } catch (e) { + this.setStatus(SyncStatus.Idle); + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.RemotePreconditionFailed) { + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Extensions: Failed to synchronize extensions as there is a new remote version available. Synchronizing again...'); + return this.sync(); + } + throw e; + } + + this.logService.trace('Extensions: Finished synchronizing extensions.'); + this.setStatus(SyncStatus.Idle); + } + + private async getPreview(remoteUserData: IUserData, lastSyncUserData: ILastSyncUserData | null): Promise { + const remoteExtensions: ISyncExtension[] = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.content!) : null; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; - const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - const remoteExtensions: ISyncExtension[] = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; - const localExtensions = await this.getLocalExtensions(); if (remoteExtensions) { @@ -202,7 +190,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const hasChanges = added.length || removed.length || updated.length || remote; if (!hasChanges) { - this.logService.trace('Extensions: No changes found during synchronizing extensions.'); + this.logService.info('Extensions: No changes found during synchronizing extensions.'); } if (added.length || removed.length || updated.length) { @@ -255,7 +243,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } else { this.logService.trace('Extensions: Disabling extension...', e.identifier.id); await this.extensionEnablementService.disableExtension(e.identifier); - this.logService.info('Extensions: Disabled extension.', e.identifier.id); + this.logService.info('Extensions: Disabled extension', e.identifier.id); } removeFromSkipped.push(e.identifier); return; @@ -307,7 +295,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private async getLocalExtensions(): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); - const disabledExtensions = await this.extensionEnablementService.getDisabledExtensionsAsync(); + const disabledExtensions = await this.extensionEnablementService.getDisabledExtensions(); return installedExtensions .map(({ identifier }) => ({ identifier, enabled: !disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)) })); } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 07c9870f85..cd1941c7e0 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -12,9 +12,9 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IStringDictionary } from 'vs/base/common/collections'; import { edit } from 'vs/platform/userDataSync/common/content'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parse } from 'vs/base/common/json'; import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const argvProperties: string[] = ['locale']; @@ -27,22 +27,23 @@ interface ISyncPreviewResult { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { + readonly resourceKey: ResourceKey = 'globalState'; + constructor( @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService); + super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); } - protected getRemoteDataResourceKey(): string { return 'globalState'; } - async pull(): Promise { - if (!this.configurationService.getValue('sync.enableUIState')) { + if (!this.enabled) { this.logService.info('UI State: Skipped pulling ui state as it is disabled.'); return; } @@ -73,7 +74,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } async push(): Promise { - if (!this.configurationService.getValue('sync.enableUIState')) { + if (!this.enabled) { this.logService.info('UI State: Skipped pushing UI State as it is disabled.'); return; } @@ -96,43 +97,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } - async sync(): Promise { - if (!this.configurationService.getValue('sync.enableUIState')) { - this.logService.trace('UI State: Skipping synchronizing UI state as it is disabled.'); - return; - } - - if (this.status !== SyncStatus.Idle) { - this.logService.trace('UI State: Skipping synchronizing ui state as it is running already.'); - return; - } - - this.logService.trace('UI State: Started synchronizing ui state...'); - this.setStatus(SyncStatus.Syncing); - - try { - const result = await this.getPreview(); - await this.apply(result); - this.logService.trace('UI State: Finished synchronizing ui state.'); - } catch (e) { - this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('UI State: Failed to synchronize ui state as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - throw e; - } finally { - this.setStatus(SyncStatus.Idle); - } - } - async stop(): Promise { } - async restart(): Promise { - throw new Error('UI State: Conflicts should not occur'); - } - accept(content: string): Promise { throw new Error('UI State: Conflicts should not occur'); } @@ -153,12 +119,27 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return null; } - private async getPreview(): Promise { - const lastSyncUserData = await this.getLastSyncUserData(); - const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null; + protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise { + try { + const result = await this.getPreview(remoteUserData, lastSyncUserData); + await this.apply(result); + this.logService.trace('UI State: Finished synchronizing ui state.'); + } catch (e) { + this.setStatus(SyncStatus.Idle); + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.RemotePreconditionFailed) { + // Rejected as there is a new remote version. Syncing again, + this.logService.info('UI State: Failed to synchronize ui state as there is a new remote version available. Synchronizing again...'); + return this.sync(); + } + throw e; + } finally { + this.setStatus(SyncStatus.Idle); + } + } - const remoteUserData = await this.getRemoteUserData(lastSyncUserData); + private async getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, ): Promise { const remoteGlobalState: IGlobalState = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; + const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null; const localGloablState = await this.getLocalGlobalState(); @@ -178,7 +159,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const hasChanges = local || remote; if (!hasChanges) { - this.logService.trace('UI State: No changes found during synchronizing ui state.'); + this.logService.info('UI State: No changes found during synchronizing ui state.'); } if (local) { @@ -208,26 +189,32 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs private async getLocalGlobalState(): Promise { const argv: IStringDictionary = {}; const storage: IStringDictionary = {}; - try { - const content = await this.fileService.readFile(this.environmentService.argvResource); - const argvValue: IStringDictionary = parse(content.value.toString()); - for (const argvProperty of argvProperties) { - if (argvValue[argvProperty] !== undefined) { - argv[argvProperty] = argvValue[argvProperty]; - } + const argvContent: string = await this.getLocalArgvContent(); + const argvValue: IStringDictionary = parse(argvContent); + for (const argvProperty of argvProperties) { + if (argvValue[argvProperty] !== undefined) { + argv[argvProperty] = argvValue[argvProperty]; } - } catch (error) { } + } return { argv, storage }; } + private async getLocalArgvContent(): Promise { + try { + const content = await this.fileService.readFile(this.environmentService.argvResource); + return content.value.toString(); + } catch (error) { } + return '{}'; + } + private async writeLocalGlobalState(globalState: IGlobalState): Promise { - const content = await this.fileService.readFile(this.environmentService.argvResource); - let argvContent = content.value.toString(); + const argvContent = await this.getLocalArgvContent(); + let content = argvContent; for (const argvProperty of Object.keys(globalState.argv)) { - argvContent = edit(argvContent, [argvProperty], globalState.argv[argvProperty], {}); + content = edit(content, [argvProperty], globalState.argv[argvProperty], {}); } - if (argvContent !== content.value.toString()) { - await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(argvContent)); + if (argvContent !== content) { + await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content)); } } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index c75dd09f0c..fec6c1ea4b 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -3,21 +3,22 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, IUserData, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; -import { parse, ParseError } from 'vs/base/common/json'; +import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { isUndefined } from 'vs/base/common/types'; -import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { URI } from 'vs/base/common/uri'; interface ISyncContent { mac?: string; @@ -26,34 +27,26 @@ interface ISyncContent { all?: string; } -interface ISyncPreviewResult { - readonly fileContent: IFileContent | null; - readonly remoteUserData: IUserData; - readonly lastSyncUserData: IUserData | null; - readonly hasLocalChanged: boolean; - readonly hasRemoteChanged: boolean; - readonly hasConflicts: boolean; -} +export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { -export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements IUserDataSynchroniser { - - private syncPreviewResultPromise: CancelablePromise | null = null; + readonly resourceKey: ResourceKey = 'keybindings'; + protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; } constructor( @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, + @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService); + super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService); } - protected getRemoteDataResourceKey(): string { return 'keybindings'; } - async pull(): Promise { - if (!this.configurationService.getValue('sync.enableKeybindings')) { + if (!this.enabled) { this.logService.info('Keybindings: Skipped pulling keybindings as it is disabled.'); return; } @@ -66,18 +59,18 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - const remoteContent = remoteUserData.content !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; + const content = remoteUserData.content !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; - if (remoteContent !== null) { - await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(remoteContent)); + if (content !== null) { const fileContent = await this.getLocalFileContent(); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, + remoteUserData, + lastSyncUserData, + content, hasConflicts: false, hasLocalChanged: true, hasRemoteChanged: false, - remoteUserData, - lastSyncUserData })); await this.apply(); } @@ -95,7 +88,7 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements } async push(): Promise { - if (!this.configurationService.getValue('sync.enableKeybindings')) { + if (!this.enabled) { this.logService.info('Keybindings: Skipped pushing keybindings as it is disabled.'); return; } @@ -111,16 +104,16 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements if (fileContent !== null) { const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, fileContent.value); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, - hasConflicts: false, + remoteUserData, + lastSyncUserData, + content: fileContent.value.toString(), hasLocalChanged: false, hasRemoteChanged: true, - remoteUserData, - lastSyncUserData + hasConflicts: false, })); - await this.apply(undefined, true); + await this.apply(true); } // No local exists to push @@ -135,53 +128,13 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements } - async sync(): Promise { - if (!this.configurationService.getValue('sync.enableKeybindings')) { - this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is disabled.'); - return; - } - - if (this.status !== SyncStatus.Idle) { - this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is running already.'); - return; - } - - this.logService.trace('Keybindings: Started synchronizing keybindings...'); - this.setStatus(SyncStatus.Syncing); - - return this.doSync(); - } - - async stop(): Promise { - if (this.syncPreviewResultPromise) { - this.syncPreviewResultPromise.cancel(); - this.syncPreviewResultPromise = null; - this.logService.trace('Keybindings: Stopped synchronizing keybindings.'); - } - await this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); - this.setStatus(SyncStatus.Idle); - } - - async restart(): Promise { - if (this.status === SyncStatus.HasConflicts) { - this.syncPreviewResultPromise!.cancel(); - this.syncPreviewResultPromise = null; - await this.doSync(); - } - } - async accept(content: string): Promise { if (this.status === SyncStatus.HasConflicts) { - try { - await this.apply(content, true); - this.setStatus(SyncStatus.Idle); - } catch (e) { - if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || - (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { - throw new UserDataSyncError('Failed to resolve conflicts as there is a new local version available.', UserDataSyncErrorCode.NewLocal); - } - throw e; - } + const preview = await this.syncPreviewResultPromise!; + this.cancel(); + this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content })); + await this.apply(true); + this.setStatus(SyncStatus.Idle); } } @@ -202,22 +155,14 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements return false; } - async getRemoteContent(): Promise { - let content: string | null | undefined = null; - if (this.syncPreviewResultPromise) { - const preview = await this.syncPreviewResultPromise; - content = preview.remoteUserData?.content; - } else { - const lastSyncData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteUserData(lastSyncData); - content = remoteUserData.content; - } - return content ? this.getKeybindingsContentFromSyncContent(content) : null; + async getRemoteContent(preview?: boolean): Promise { + const content = await super.getRemoteContent(preview); + return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null; } - private async doSync(): Promise { + protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise { try { - const result = await this.getPreview(); + const result = await this.getPreview(remoteUserData, lastSyncUserData); if (result.hasConflicts) { this.logService.info('Keybindings: Detected conflicts while synchronizing keybindings.'); this.setStatus(SyncStatus.HasConflicts); @@ -232,44 +177,32 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements } catch (e) { this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || - (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { - // Rejected as there is a new local version. Syncing again. - this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...'); - return this.sync(); + if (e instanceof UserDataSyncError) { + switch (e.code) { + case UserDataSyncErrorCode.RemotePreconditionFailed: + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new remote version available. Synchronizing again...'); + return this.sync(); + case UserDataSyncErrorCode.LocalPreconditionFailed: + // Rejected as there is a new local version. Syncing again. + this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...'); + return this.sync(remoteUserData.ref); + } } throw e; } } - private async apply(content?: string, forcePush?: boolean): Promise { + private async apply(forcePush?: boolean): Promise { if (!this.syncPreviewResultPromise) { return; } - let { fileContent, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; + let { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; - if (content === undefined) { - if (await this.fileService.exists(this.environmentService.keybindingsSyncPreviewResource)) { - const keybindingsPreivew = await this.fileService.readFile(this.environmentService.keybindingsSyncPreviewResource); - content = keybindingsPreivew.value.toString(); - } - } - - if (content !== undefined) { + if (content !== null) { if (this.hasErrors(content)) { - const error = new Error(localize('errorInvalidKeybindings', "Unable to sync keybindings. Please resolve conflicts without any errors/warnings and try again.")); - this.logService.error(error); - throw error; - } - - if (!hasLocalChanged && !hasRemoteChanged) { - this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); } if (hasLocalChanged) { @@ -287,14 +220,16 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements } // Delete the preview - await this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); + try { + await this.fileService.del(this.conflictsPreviewResource); + } catch (e) { /* ignore */ } } else { - this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); + this.logService.info('Keybindings: No changes found during synchronizing keybindings.'); } - if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== undefined || fileContent !== null)) { + if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) { this.logService.trace('Keybindings: Updating last synchronized keybindings...'); - const lastSyncContent = this.updateSyncContent(content !== undefined ? content : fileContent!.value.toString(), null); + const lastSyncContent = this.updateSyncContent(content !== null ? content : fileContent!.value.toString(), null); await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent }); this.logService.info('Keybindings: Updated last synchronized keybindings'); } @@ -302,36 +237,29 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements this.syncPreviewResultPromise = null; } - private hasErrors(content: string): boolean { - const parseErrors: ParseError[] = []; - parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); - return parseErrors.length > 0; - } - - private getPreview(): Promise { + private getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise { if (!this.syncPreviewResultPromise) { - this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token)); + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token)); } return this.syncPreviewResultPromise; } - private async generatePreview(token: CancellationToken): Promise { - const lastSyncUserData = await this.getLastSyncUserData(); - const lastSyncContent = lastSyncUserData && lastSyncUserData.content ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.content) : null; - const remoteUserData = await this.getRemoteUserData(lastSyncUserData); + private async generatePreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, token: CancellationToken): Promise { const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; + const lastSyncContent = lastSyncUserData && lastSyncUserData.content ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.content) : null; // Get file content last to get the latest const fileContent = await this.getLocalFileContent(); + const formattingOptions = await this.getFormattingOptions(); + + let content: string | null = null; let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; - let previewContent = null; if (remoteContent) { const localContent: string = fileContent ? fileContent.value.toString() : '[]'; if (this.hasErrors(localContent)) { - this.logService.error('Keybindings: Unable to sync keybindings as there are errors/warning in keybindings file.'); - return { fileContent, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); } if (!lastSyncContent // First time sync @@ -339,14 +267,13 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements || lastSyncContent !== remoteContent // Remote has forwarded ) { this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); - const formattingOptions = await this.getFormattingOptions(); const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); // Sync only if there are changes if (result.hasChanges) { - hasLocalChanged = result.mergeContent !== localContent; - hasRemoteChanged = result.mergeContent !== remoteContent; + content = result.mergeContent; hasConflicts = result.hasConflicts; - previewContent = result.mergeContent; + hasLocalChanged = hasConflicts || result.mergeContent !== localContent; + hasRemoteChanged = hasConflicts || result.mergeContent !== remoteContent; } } } @@ -354,23 +281,15 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements // First time syncing to remote else if (fileContent) { this.logService.trace('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.'); + content = fileContent.value.toString(); hasRemoteChanged = true; - previewContent = fileContent.value.toString(); } - if (previewContent && !token.isCancellationRequested) { - await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(previewContent)); + if (content && !token.isCancellationRequested) { + await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(content)); } - return { fileContent, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; - } - - private _formattingOptions: Promise | undefined = undefined; - private getFormattingOptions(): Promise { - if (!this._formattingOptions) { - this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource); - } - return this._formattingOptions; + return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; } private getKeybindingsContentFromSyncContent(syncContent: string): string | null { diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index 9803127224..5c9135a386 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -10,7 +10,7 @@ import { values } from 'vs/base/common/map'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions, Edit, getEOL } from 'vs/base/common/jsonFormatter'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; -import { IConflictSetting, DEFAULT_IGNORED_SETTINGS } from 'vs/platform/userDataSync/common/userDataSync'; +import { IConflictSetting, CONFIGURATION_SYNC_STORE_KEY } from 'vs/platform/userDataSync/common/userDataSync'; import { firstIndex } from 'vs/base/common/arrays'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { startsWith } from 'vs/base/common/strings'; @@ -42,7 +42,7 @@ export function getIgnoredSettings(configurationService: IConfigurationService, } } } - return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1); + return [CONFIGURATION_SYNC_STORE_KEY, ...added].filter(setting => removed.indexOf(setting) === -1); } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 995c9563d1..9d77847658 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -3,40 +3,31 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, IUserData, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; -import { parse, ParseError } from 'vs/base/common/json'; +import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; -import { isEmptyObject, isUndefinedOrNull } from 'vs/base/common/types'; +import { isEmptyObject } from 'vs/base/common/types'; import { edit } from 'vs/platform/userDataSync/common/content'; -import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { URI } from 'vs/base/common/uri'; -interface ISyncPreviewResult { - readonly fileContent: IFileContent | null; - readonly remoteUserData: IUserData; - readonly lastSyncUserData: IUserData | null; - readonly content: string | null; - readonly hasLocalChanged: boolean; - readonly hasRemoteChanged: boolean; - readonly hasConflicts: boolean; - readonly conflictSettings: IConflictSetting[]; -} - -export class SettingsSynchroniser extends AbstractFileSynchroniser implements ISettingsSyncService { +export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService { _serviceBrand: any; - private syncPreviewResultPromise: CancelablePromise | null = null; + readonly resourceKey: ResourceKey = 'settings'; + protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; } private _conflicts: IConflictSetting[] = []; get conflicts(): IConflictSetting[] { return this._conflicts; } @@ -47,15 +38,15 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService); + super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService); } - protected getRemoteDataResourceKey(): string { return 'settings'; } - protected setStatus(status: SyncStatus): void { super.setStatus(status); if (this.status !== SyncStatus.HasConflicts) { @@ -73,7 +64,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } async pull(): Promise { - if (!this.configurationService.getValue('sync.enableSettings')) { + if (!this.enabled) { this.logService.info('Settings: Skipped pulling settings as it is disabled.'); return; } @@ -92,7 +83,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS const formatUtils = await this.getFormattingOptions(); // Update ignored settings from local file content const content = updateIgnoredSettings(remoteUserData.content, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, remoteUserData, lastSyncUserData, @@ -100,7 +91,6 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS hasLocalChanged: true, hasRemoteChanged: false, hasConflicts: false, - conflictSettings: [], })); await this.apply(); @@ -118,7 +108,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } async push(): Promise { - if (!this.configurationService.getValue('sync.enableSettings')) { + if (!this.enabled) { this.logService.info('Settings: Skipped pushing settings as it is disabled.'); return; } @@ -138,7 +128,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, remoteUserData, lastSyncUserData, @@ -146,7 +136,6 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS hasRemoteChanged: true, hasLocalChanged: false, hasConflicts: false, - conflictSettings: [], })); await this.apply(true); @@ -163,32 +152,6 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } } - async sync(): Promise { - if (!this.configurationService.getValue('sync.enableSettings')) { - this.logService.trace('Settings: Skipping synchronizing settings as it is disabled.'); - return; - } - - if (this.status !== SyncStatus.Idle) { - this.logService.trace('Settings: Skipping synchronizing settings as it is running already.'); - return; - } - - this.logService.trace('Settings: Started synchronizing settings...'); - this.setStatus(SyncStatus.Syncing); - return this.doSync([]); - } - - async stop(): Promise { - if (this.syncPreviewResultPromise) { - this.syncPreviewResultPromise.cancel(); - this.syncPreviewResultPromise = null; - this.logService.trace('Settings: Stopped synchronizing settings.'); - } - await this.fileService.del(this.environmentService.settingsSyncPreviewResource); - this.setStatus(SyncStatus.Idle); - } - async hasLocalData(): Promise { try { const localFileContent = await this.getLocalFileContent(); @@ -209,62 +172,39 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } async getRemoteContent(preview?: boolean): Promise { - let content: string | null | undefined = null; - if (this.syncPreviewResultPromise) { - const preview = await this.syncPreviewResultPromise; - content = preview.remoteUserData?.content; - } else { - const lastSyncData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteUserData(lastSyncData); - content = remoteUserData.content; - } - if (preview && !isUndefinedOrNull(content)) { + let content = await super.getRemoteContent(preview); + if (preview && content !== null) { const formatUtils = await this.getFormattingOptions(); // remove ignored settings from the remote content for preview content = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils); } - return content !== undefined ? content : null; - } - - async restart(): Promise { - if (this.status === SyncStatus.HasConflicts) { - this.syncPreviewResultPromise!.cancel(); - this.syncPreviewResultPromise = null; - await this.doSync([]); - } + return content; } async accept(content: string): Promise { if (this.status === SyncStatus.HasConflicts) { - try { - const preview = await this.syncPreviewResultPromise!; - const formatUtils = await this.getFormattingOptions(); - // Add ignored settings from local file content - content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); - this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content })); - await this.apply(true); - this.setStatus(SyncStatus.Idle); - } catch (e) { - if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || - (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { - throw new UserDataSyncError('Failed to resolve conflicts as there is a new local version available.', UserDataSyncErrorCode.NewLocal); - } - throw e; - } + const preview = await this.syncPreviewResultPromise!; + this.cancel(); + const formatUtils = await this.getFormattingOptions(); + // Add ignored settings from local file content + content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); + this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content })); + await this.apply(true); + this.setStatus(SyncStatus.Idle); } } async resolveSettingsConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { if (this.status === SyncStatus.HasConflicts) { - this.syncPreviewResultPromise!.cancel(); - this.syncPreviewResultPromise = null; - await this.doSync(resolvedConflicts); + const preview = await this.syncPreviewResultPromise!; + this.cancel(); + await this.doSync(preview.remoteUserData, preview.lastSyncUserData, resolvedConflicts); } } - private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { + protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise { try { - const result = await this.getPreview(resolvedConflicts); + const result = await this.getPreview(remoteUserData, lastSyncUserData, resolvedConflicts); if (result.hasConflicts) { this.logService.info('Settings: Detected conflicts while synchronizing settings.'); this.setStatus(SyncStatus.HasConflicts); @@ -279,16 +219,17 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } catch (e) { this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Settings: Failed to synchronize settings as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || - (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { - // Rejected as there is a new local version. Syncing again. - this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...'); - return this.sync(); + if (e instanceof UserDataSyncError) { + switch (e.code) { + case UserDataSyncErrorCode.RemotePreconditionFailed: + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Settings: Failed to synchronize settings as there is a new remote version available. Synchronizing again...'); + return this.sync(); + case UserDataSyncErrorCode.LocalPreconditionFailed: + // Rejected as there is a new local version. Syncing again. + this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...'); + return this.sync(remoteUserData.ref); + } } throw e; } @@ -304,9 +245,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS if (content !== null) { if (this.hasErrors(content)) { - const error = new Error(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again.")); - this.logService.error(error); - throw error; + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); } if (hasLocalChanged) { @@ -325,9 +264,11 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } // Delete the preview - await this.fileService.del(this.environmentService.settingsSyncPreviewResource); + try { + await this.fileService.del(this.conflictsPreviewResource); + } catch (e) { /* ignore */ } } else { - this.logService.trace('Settings: No changes found during synchronizing settings.'); + this.logService.info('Settings: No changes found during synchronizing settings.'); } if (lastSyncUserData?.ref !== remoteUserData.ref) { @@ -339,25 +280,16 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS this.syncPreviewResultPromise = null; } - private hasErrors(content: string): boolean { - const parseErrors: ParseError[] = []; - parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); - return parseErrors.length > 0; - } - - private getPreview(resolvedConflicts: { key: string, value: any }[]): Promise { + private getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any }[]): Promise { if (!this.syncPreviewResultPromise) { - this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(resolvedConflicts, token)); + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, resolvedConflicts, token)); } return this.syncPreviewResultPromise; } - private async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise { - const lastSyncUserData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - // Get file content last to get the latest + protected async generatePreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise { const fileContent = await this.getLocalFileContent(); - const formatUtils = await this.getFormattingOptions(); + const formattingOptions = await this.getFormattingOptions(); let content: string | null = null; let hasLocalChanged: boolean = false; @@ -370,12 +302,12 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS // No action when there are errors if (this.hasErrors(localContent)) { - this.logService.error('Settings: Unable to sync settings as there are errors/warning in settings file.'); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); } else { this.logService.trace('Settings: Merging remote settings with local settings...'); - const result = merge(localContent, remoteUserData.content, lastSyncUserData ? lastSyncUserData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formatUtils); + const result = merge(localContent, remoteUserData.content, lastSyncUserData ? lastSyncUserData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions); content = result.localContent || result.remoteContent; hasLocalChanged = result.localContent !== null; hasRemoteChanged = result.remoteContent !== null; @@ -393,20 +325,12 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS if (content && !token.isCancellationRequested) { // Remove the ignored settings from the preview. - const previewContent = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils); + const previewContent = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formattingOptions); await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent)); } this.setConflicts(conflictSettings); - return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, conflictSettings, hasConflicts }; - } - - private _formattingOptions: Promise | undefined = undefined; - private getFormattingOptions(): Promise { - if (!this._formattingOptions) { - this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.settingsResource); - } - return this._formattingOptions; + return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; } } diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index d1c2f2f1f6..5a49473ce1 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -6,8 +6,7 @@ import { timeout, Delayer } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService, UserDataSyncError, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncSource, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService { @@ -21,18 +20,18 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event; constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, - @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, ) { super(); this.updateEnablement(false, true); this.syncDelayer = this._register(new Delayer(0)); this._register(Event.any(userDataAuthTokenService.onDidChangeToken)(() => this.updateEnablement(true, true))); this._register(Event.any(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true, true))); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true, false))); + this._register(this.userDataSyncEnablementService.onDidChangeEnablement(() => this.updateEnablement(true, false))); + this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync())); } private async updateEnablement(stopIfDisabled: boolean, auto: boolean): Promise { @@ -59,24 +58,22 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto private async sync(loop: boolean, auto: boolean): Promise { if (this.enabled) { try { - if (auto) { - if (await this.isTurnedOffEverywhere()) { - // Turned off everywhere. Reset & Stop Sync. - this.logService.info('Auto Sync: Turning off sync as it is turned off everywhere.'); - await this.userDataSyncService.resetLocal(); - await this.userDataSyncUtilService.updateConfigurationValue('sync.enable', false); - return; - } - if (this.userDataSyncService.status !== SyncStatus.Idle) { - this.logService.trace('Auto Sync: Skipped once as it is syncing already'); - return; - } - } await this.userDataSyncService.sync(); this.resetFailures(); } catch (e) { - this.successiveFailures++; + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.TurnedOff) { + this.logService.info('Auto Sync: Sync is turned off in the cloud.'); + this.logService.info('Auto Sync: Resetting the local sync state.'); + await this.userDataSyncService.resetLocal(); + this.logService.info('Auto Sync: Completed resetting the local sync state.'); + if (auto) { + return this.userDataSyncEnablementService.setEnablement(false); + } else { + return this.sync(loop, auto); + } + } this.logService.error(e); + this.successiveFailures++; this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); } if (loop) { @@ -88,14 +85,8 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } } - private async isTurnedOffEverywhere(): Promise { - const hasRemote = await this.userDataSyncService.hasRemoteData(); - const hasPreviouslySynced = await this.userDataSyncService.hasPreviouslySynced(); - return !hasRemote && hasPreviouslySynced; - } - private async isAutoSyncEnabled(): Promise { - return this.configurationService.getValue('sync.enable') + return this.userDataSyncEnablementService.isEnabled() && this.userDataSyncService.status !== SyncStatus.Uninitialized && !!(await this.userDataAuthTokenService.getToken()); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 100aa8461f..aecbb0cb43 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -19,16 +19,10 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; -export const DEFAULT_IGNORED_SETTINGS = [ - CONFIGURATION_SYNC_STORE_KEY, - 'sync.enable', - 'sync.enableSettings', - 'sync.enableExtensions', -]; - export interface ISyncConfiguration { sync: { enable: boolean, @@ -53,33 +47,33 @@ export function registerConfiguration(): IDisposable { properties: { 'sync.enable': { type: 'boolean', - description: localize('sync.enable', "Enable synchronization."), default: false, - scope: ConfigurationScope.APPLICATION + scope: ConfigurationScope.APPLICATION, + deprecationMessage: 'deprecated' }, 'sync.enableSettings': { type: 'boolean', - description: localize('sync.enableSettings', "Enable synchronizing settings."), default: true, scope: ConfigurationScope.APPLICATION, + deprecationMessage: 'deprecated' }, 'sync.enableKeybindings': { type: 'boolean', - description: localize('sync.enableKeybindings', "Enable synchronizing keybindings."), default: true, scope: ConfigurationScope.APPLICATION, + deprecationMessage: 'Deprecated' }, 'sync.enableUIState': { type: 'boolean', - description: localize('sync.enableUIState', "Enable synchronizing UI state (Only Display Language)."), default: true, scope: ConfigurationScope.APPLICATION, + deprecationMessage: 'deprecated' }, 'sync.enableExtensions': { type: 'boolean', - description: localize('sync.enableExtensions', "Enable synchronizing extensions."), default: true, scope: ConfigurationScope.APPLICATION, + deprecationMessage: 'deprecated' }, 'sync.keybindingsPerPlatform': { type: 'boolean', @@ -96,7 +90,7 @@ export function registerConfiguration(): IDisposable { }, 'sync.ignoredSettings': { 'type': 'array', - description: localize('sync.ignoredSettings', "Configure settings to be ignored while synchronizing. \nDefault Ignored Settings:\n\n{0}", DEFAULT_IGNORED_SETTINGS.sort().map(setting => `- ${setting}`).join('\n')), + description: localize('sync.ignoredSettings', "Configure settings to be ignored while synchronizing."), 'default': [], 'scope': ConfigurationScope.APPLICATION, $ref: ignoredSettingsSchemaId, @@ -110,7 +104,7 @@ export function registerConfiguration(): IDisposable { const ignoredSettingsSchema: IJSONSchema = { items: { type: 'string', - enum: [...Object.keys(allSettings.properties).filter(setting => DEFAULT_IGNORED_SETTINGS.indexOf(setting) === -1), ...DEFAULT_IGNORED_SETTINGS.map(setting => `-${setting}`)] + enum: Object.keys(allSettings.properties) } }; jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema); @@ -118,19 +112,61 @@ export function registerConfiguration(): IDisposable { return configurationRegistry.onDidUpdateConfiguration(() => registerIgnoredSettingsSchema()); } +// #region User Data Sync Store + export interface IUserData { ref: string; content: string | null; } +export interface IUserDataSyncStore { + url: string; + authenticationProviderId: string; +} + +export function getUserDataSyncStore(configurationService: IConfigurationService): IUserDataSyncStore | undefined { + const value = configurationService.getValue(CONFIGURATION_SYNC_STORE_KEY); + return value && value.url && value.authenticationProviderId ? value : undefined; +} + +export const ALL_RESOURCE_KEYS: ResourceKey[] = ['settings', 'keybindings', 'extensions', 'globalState']; +export type ResourceKey = 'settings' | 'keybindings' | 'extensions' | 'globalState'; + +export interface IUserDataManifest { + settings: string; + keybindings: string; + extensions: string; + globalState: string; +} + +export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); +export interface IUserDataSyncStoreService { + _serviceBrand: undefined; + readonly userDataSyncStore: IUserDataSyncStore | undefined; + read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise; + write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise; + manifest(): Promise; + clear(): Promise; +} + +//#endregion + +// #region User Data Sync Error + export enum UserDataSyncErrorCode { + // Server Errors Unauthorized = 'Unauthorized', Forbidden = 'Forbidden', ConnectionRefused = 'ConnectionRefused', - Rejected = 'Rejected', + RemotePreconditionFailed = 'RemotePreconditionFailed', TooLarge = 'TooLarge', NoRef = 'NoRef', - NewLocal = 'NewLocal', + TurnedOff = 'TurnedOff', + + // Local Errors + LocalPreconditionFailed = 'LocalPreconditionFailed', + LocalInvalidContent = 'LocalInvalidContent', + Unknown = 'Unknown', } @@ -156,24 +192,9 @@ export class UserDataSyncError extends Error { export class UserDataSyncStoreError extends UserDataSyncError { } -export interface IUserDataSyncStore { - url: string; - authenticationProviderId: string; -} +//#endregion -export function getUserDataSyncStore(configurationService: IConfigurationService): IUserDataSyncStore | undefined { - const value = configurationService.getValue(CONFIGURATION_SYNC_STORE_KEY); - return value && value.url && value.authenticationProviderId ? value : undefined; -} - -export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); -export interface IUserDataSyncStoreService { - _serviceBrand: undefined; - readonly userDataSyncStore: IUserDataSyncStore | undefined; - read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise; - write(key: string, content: string, ref: string | null, source?: SyncSource): Promise; - clear(): Promise; -} +// #region User Data Synchroniser export interface ISyncExtension { identifier: IExtensionIdentifier; @@ -200,34 +221,64 @@ export const enum SyncStatus { HasConflicts = 'hasConflicts', } -export interface ISynchroniser { +export interface IUserDataSynchroniser { + + readonly resourceKey: ResourceKey; + readonly source: SyncSource; readonly status: SyncStatus; readonly onDidChangeStatus: Event; readonly onDidChangeLocal: Event; + pull(): Promise; push(): Promise; - sync(): Promise; + sync(ref?: string): Promise; stop(): Promise; - restart(): Promise; + hasPreviouslySynced(): Promise - hasRemoteData(): Promise; hasLocalData(): Promise; resetLocal(): Promise; -} -export interface IUserDataSynchroniser extends ISynchroniser { - readonly source: SyncSource; getRemoteContent(preivew?: boolean): Promise; accept(content: string): Promise; } -export const IUserDataSyncService = createDecorator('IUserDataSyncService'); -export interface IUserDataSyncService extends ISynchroniser { +//#endregion + +// #region User Data Sync Services + +export const IUserDataSyncEnablementService = createDecorator('IUserDataSyncEnablementService'); +export interface IUserDataSyncEnablementService { _serviceBrand: any; - readonly conflictsSource: SyncSource | null; - isFirstTimeSyncAndHasUserData(): Promise; + + readonly onDidChangeEnablement: Event; + readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]>; + + isEnabled(): boolean; + setEnablement(enabled: boolean): void; + + isResourceEnabled(key: ResourceKey): boolean; + setResourceEnablement(key: ResourceKey, enabled: boolean): void; +} + +export const IUserDataSyncService = createDecorator('IUserDataSyncService'); +export interface IUserDataSyncService { + _serviceBrand: any; + + readonly status: SyncStatus; + readonly onDidChangeStatus: Event; + + readonly conflictsSources: SyncSource[]; + readonly onDidChangeConflicts: Event; + + readonly onDidChangeLocal: Event; + + pull(): Promise; + sync(): Promise; + stop(): Promise; reset(): Promise; resetLocal(): Promise; + + isFirstTimeSyncWithMerge(): Promise; getRemoteContent(source: SyncSource, preview: boolean): Promise; accept(source: SyncSource, content: string): Promise; } @@ -242,7 +293,6 @@ export interface IUserDataAutoSyncService { export const IUserDataSyncUtilService = createDecorator('IUserDataSyncUtilService'); export interface IUserDataSyncUtilService { _serviceBrand: undefined; - updateConfigurationValue(key: string, value: any): Promise; resolveUserBindings(userbindings: string[]): Promise>; resolveFormattingOptions(resource: URI): Promise; } @@ -275,6 +325,8 @@ export interface ISettingsSyncService extends IUserDataSynchroniser { resolveSettingsConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; } +//#endregion + export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; @@ -284,3 +336,12 @@ export function toRemoteContentResource(source: SyncSource): URI { export function getSyncSourceFromRemoteContentResource(uri: URI): SyncSource | undefined { return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.GlobalState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0]; } +export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncSource | undefined { + if (isEqual(uri, environmentService.settingsSyncPreviewResource)) { + return SyncSource.Settings; + } + if (isEqual(uri, environmentService.keybindingsSyncPreviewResource)) { + return SyncSource.Keybindings; + } + return undefined; +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts new file mode 100644 index 0000000000..d934965836 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncEnablementService, ResourceKey, ALL_RESOURCE_KEYS } from 'vs/platform/userDataSync/common/userDataSync'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; + +const enablementKey = 'sync.enable'; +function getEnablementKey(resourceKey: ResourceKey) { return `${enablementKey}.${resourceKey}`; } + +export class UserDataSyncEnablementService extends Disposable implements IUserDataSyncEnablementService { + + _serviceBrand: any; + + private _onDidChangeEnablement = new Emitter(); + readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; + + private _onDidChangeResourceEnablement = new Emitter<[ResourceKey, boolean]>(); + readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event; + + constructor( + @IStorageService private readonly storageService: IStorageService + ) { + super(); + this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + } + + isEnabled(): boolean { + return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, false); + } + + setEnablement(enabled: boolean): void { + if (this.isEnabled() !== enabled) { + this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL); + } + } + + isResourceEnabled(resourceKey: ResourceKey): boolean { + return this.storageService.getBoolean(getEnablementKey(resourceKey), StorageScope.GLOBAL, true); + } + + setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void { + if (this.isResourceEnabled(resourceKey) !== enabled) { + this.storageService.store(getEnablementKey(resourceKey), enabled, StorageScope.GLOBAL); + } + } + + private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void { + if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) { + if (enablementKey === workspaceStorageChangeEvent.key) { + this._onDidChangeEnablement.fire(this.isEnabled()); + return; + } + const resourceKey = ALL_RESOURCE_KEYS.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0]; + if (resourceKey) { + this._onDidChangeResourceEnablement.fire([resourceKey, this.isEnabled()]); + return; + } + } + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 2e34785f8c..5ca22ea41c 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -17,6 +17,7 @@ export class UserDataSyncChannel implements IServerChannel { listen(_: unknown, event: string): Event { switch (event) { case 'onDidChangeStatus': return this.service.onDidChangeStatus; + case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; case 'onDidChangeLocal': return this.service.onDidChangeLocal; } throw new Error(`Event not found: ${event}`); @@ -24,21 +25,15 @@ export class UserDataSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { + case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflictsSources]); case 'sync': return this.service.sync(); case 'accept': return this.service.accept(args[0], args[1]); case 'pull': return this.service.pull(); - case 'push': return this.service.push(); - case '_getInitialStatus': return Promise.resolve(this.service.status); - case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); case 'stop': this.service.stop(); return Promise.resolve(); - case 'restart': return this.service.restart().then(() => this.service.status); case 'reset': return this.service.reset(); case 'resetLocal': return this.service.resetLocal(); - case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); - case 'hasRemoteData': return this.service.hasRemoteData(); - case 'hasLocalData': return this.service.hasLocalData(); case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]); - case 'isFirstTimeSyncAndHasUserData': return this.service.isFirstTimeSyncAndHasUserData(); + case 'isFirstTimeSyncWithMerge': return this.service.isFirstTimeSyncWithMerge(); } throw new Error('Invalid call'); } @@ -63,13 +58,11 @@ export class SettingsSyncChannel implements IServerChannel { case 'accept': return this.service.accept(args[0]); case 'pull': return this.service.pull(); case 'push': return this.service.push(); - case 'restart': return this.service.restart().then(() => this.service.status); case '_getInitialStatus': return Promise.resolve(this.service.status); case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); case 'stop': this.service.stop(); return Promise.resolve(); case 'resetLocal': return this.service.resetLocal(); case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); - case 'hasRemoteData': return this.service.hasRemoteData(); case 'hasLocalData': return this.service.hasLocalData(); case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]); case 'getRemoteContent': return this.service.getRemoteContent(args[0]); @@ -128,7 +121,6 @@ export class UserDataSycnUtilServiceChannel implements IServerChannel { switch (command) { case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]); case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0])); - case 'updateConfigurationValue': return this.service.updateConfigurationValue(args[0], args[1]); } throw new Error('Invalid call'); } @@ -149,9 +141,5 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { return this.channel.call('resolveFormattingOptions', [file]); } - async updateConfigurationValue(key: string, value: any): Promise { - return this.channel.call('updateConfigurationValue', [key, value]); - } - } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index c32fd19dd7..ae4a9a426d 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -11,12 +11,9 @@ import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensio import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { localize } from 'vs/nls'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - -type SyncConflictsClassification = { - source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; -}; +import { equals } from 'vs/base/common/arrays'; +import { localize } from 'vs/nls'; type SyncErrorClassification = { source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -35,8 +32,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ readonly onDidChangeLocal: Event; - private _conflictsSource: SyncSource | null = null; - get conflictsSource(): SyncSource | null { return this._conflictsSource; } + private _conflictsSources: SyncSource[] = []; + get conflictsSources(): SyncSource[] { return this._conflictsSources; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; private readonly keybindingsSynchroniser: KeybindingsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; @@ -66,12 +65,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async pull(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); - } + await this.checkEnablement(); for (const synchroniser of this.synchronisers) { try { await synchroniser.pull(); @@ -82,12 +76,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async push(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); - } + await this.checkEnablement(); for (const synchroniser of this.synchronisers) { try { await synchroniser.push(); @@ -98,105 +87,79 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async sync(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); - } - if (this.status === SyncStatus.HasConflicts) { - throw new Error(localize('resolve conflicts', "Please resolve conflicts before resuming sync.")); - } - const startTime = new Date().getTime(); - this.logService.trace('Started Syncing...'); - for (const synchroniser of this.synchronisers) { - try { - await synchroniser.sync(); - // do not continue if synchroniser has conflicts - if (synchroniser.status === SyncStatus.HasConflicts) { - break; - } - } catch (e) { - this.handleSyncError(e, synchroniser.source); - } - } - this.logService.trace(`Finished Syncing. Took ${new Date().getTime() - startTime}ms`); - } + await this.checkEnablement(); - async accept(source: SyncSource, content: string): Promise { - const synchroniser = this.getSynchroniser(source); - await synchroniser.accept(content); - if (synchroniser.status !== SyncStatus.HasConflicts) { - await this.sync(); + const startTime = new Date().getTime(); + try { + this.logService.trace('Sync started.'); + if (this.status !== SyncStatus.HasConflicts) { + this.setStatus(SyncStatus.Syncing); + } + + const manifest = await this.userDataSyncStoreService.manifest(); + + // Server has no data but this machine was synced before + if (manifest === null && await this.hasPreviouslySynced()) { + // Sync was turned off from other machine + throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff); + } + + for (const synchroniser of this.synchronisers) { + try { + await synchroniser.sync(manifest ? manifest[synchroniser.resourceKey] : undefined); + } catch (e) { + this.handleSyncError(e, synchroniser.source); + } + } + + this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`); + + } finally { + this.updateStatus(); } } async stop(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); + if (this.status === SyncStatus.Idle) { + return; } for (const synchroniser of this.synchronisers) { - await synchroniser.stop(); + try { + if (synchroniser.status !== SyncStatus.Idle) { + await synchroniser.stop(); + } + } catch (e) { + this.logService.error(e); + } } } - async restart(): Promise { - const synchroniser = this.getSynchroniserInConflicts(); - if (!synchroniser) { - throw new Error(localize('no synchroniser with conflicts', "No conflicts detected.")); - } - await synchroniser.restart(); - if (synchroniser.status !== SyncStatus.HasConflicts) { - await this.sync(); - } + async accept(source: SyncSource, content: string): Promise { + await this.checkEnablement(); + const synchroniser = this.getSynchroniser(source); + return synchroniser.accept(content); } async hasPreviouslySynced(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); - } + await this.checkEnablement(); for (const synchroniser of this.synchronisers) { if (await synchroniser.hasPreviouslySynced()) { - return true; } } return false; } - async hasRemoteData(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); - } - for (const synchroniser of this.synchronisers) { - if (await synchroniser.hasRemoteData()) { - return true; - } - } - return false; - } - - async hasLocalData(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); - } + private async hasLocalData(): Promise { + await this.checkEnablement(); for (const synchroniser of this.synchronisers) { if (await synchroniser.hasLocalData()) { - return true; } } return false; } async getRemoteContent(source: SyncSource, preview: boolean): Promise { + await this.checkEnablement(); for (const synchroniser of this.synchronisers) { if (synchroniser.source === source) { return synchroniser.getRemoteContent(preview); @@ -205,12 +168,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return null; } - async isFirstTimeSyncAndHasUserData(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); + async isFirstTimeSyncWithMerge(): Promise { + await this.checkEnablement(); + if (!await this.userDataSyncStoreService.manifest()) { + return false; } if (await this.hasPreviouslySynced()) { return false; @@ -219,60 +180,46 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async reset(): Promise { + await this.checkEnablement(); await this.resetRemote(); await this.resetLocal(); } - private async resetRemote(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); - } - try { - await this.userDataSyncStoreService.clear(); - this.logService.info('Completed clearing remote data'); - } catch (e) { - this.logService.error(e); - } - } - async resetLocal(): Promise { - if (!this.userDataSyncStoreService.userDataSyncStore) { - throw new Error('Not enabled'); - } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new Error('Not Authenticated. Please sign in to start sync.'); - } + await this.checkEnablement(); for (const synchroniser of this.synchronisers) { try { - await synchroniser.resetLocal(); } catch (e) { this.logService.error(`${synchroniser.source}: ${toErrorMessage(e)}`); this.logService.error(e); } } - this.logService.info('Completed resetting local cache'); + } + + private async resetRemote(): Promise { + await this.checkEnablement(); + try { + await this.userDataSyncStoreService.clear(); + } catch (e) { + this.logService.error(e); + } + } + + private setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangeStatus.fire(status); + } } private updateStatus(): void { - const status = this.computeStatus(); - if (this._status !== status) { - const oldStatus = this._status; - const oldConflictsSource = this._conflictsSource; - this._conflictsSource = this.computeConflictsSource(); - this._status = status; - if (status === SyncStatus.HasConflicts) { - // Log to telemetry when there is a sync conflict - this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsDetected', { source: this._conflictsSource! }); - } - if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { - // Log to telemetry when conflicts are resolved - this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsResolved', { source: oldConflictsSource! }); - } - this._onDidChangeStatus.fire(status); + const conflictsSources = this.computeConflictsSources(); + if (!equals(this._conflictsSources, conflictsSources)) { + this._conflictsSources = this.computeConflictsSources(); + this._onDidChangeConflicts.fire(conflictsSources); } + const status = this.computeStatus(); + this.setStatus(status); } private computeStatus(): SyncStatus { @@ -300,14 +247,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.error(`${source}: ${toErrorMessage(e)}`); } - private computeConflictsSource(): SyncSource | null { - const synchroniser = this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)[0]; - return synchroniser ? synchroniser.source : null; - } - - private getSynchroniserInConflicts(): IUserDataSynchroniser | null { - const synchroniser = this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)[0]; - return synchroniser || null; + private computeConflictsSources(): SyncSource[] { + return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.source); } private getSynchroniser(source: SyncSource): IUserDataSynchroniser { @@ -319,6 +260,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } + private async checkEnablement(): Promise { + if (!this.userDataSyncStoreService.userDataSyncStore) { + throw new Error('Not enabled'); + } + if (!(await this.userDataAuthTokenService.getToken())) { + throw new UserDataSyncError('Not Authenticated. Please sign in to start sync.', UserDataSyncErrorCode.Unauthorized); + } + } + private onDidChangeAuthTokenStatus(token: string | undefined): void { if (!token) { this.stop(); diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 20b33b9e4c..f423e76d6b 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; -import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync'; +import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -84,6 +84,22 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return newRef; } + async manifest(): Promise { + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); + } + + const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', 'latest').toString(); + const headers: IHeaders = { 'Content-Type': 'application/json' }; + + const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None); + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown); + } + + return asJson(context); + } + async clear(): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); @@ -107,7 +123,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn options.headers = options.headers || {}; options.headers['authorization'] = `Bearer ${authToken}`; - this.logService.trace('Sending request to server', { url: options.url, headers: { ...options.headers, ...{ authorization: undefined } } }); + this.logService.trace('Sending request to server', { url: options.url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } }); let context; try { @@ -126,7 +142,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } if (context.res.statusCode === 412) { - throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Precondition Failed (412). There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Rejected, source); + throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Precondition Failed (412). There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.RemotePreconditionFailed, source); } if (context.res.statusCode === 413) { diff --git a/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts index b8078812ef..c04d3a592f 100644 --- a/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -3,23 +3,21 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { constructor( + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService userDataSyncService: IUserDataSyncService, @IElectronService electronService: IElectronService, - @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService, - @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, ) { - super(configurationService, userDataSyncService, logService, authTokenService, userDataSyncUtilService); + super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService); // Sync immediately if there is a local change. this._register(Event.debounce(Event.any( diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index b79207e1ed..1e7c456bbd 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -638,7 +638,5 @@ class MockUserDataSyncUtilService implements IUserDataSyncUtilService { return { eol: '\n', insertSpaces: false, tabSize: 4 }; } - async updateConfigurationValue(key: string, value: any): Promise { } - async ignoreExtensionsToSync(extensions: IExtensionIdentifier[]): Promise { } } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index f0930f1864..3e9fa9d8e9 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2135,6 +2135,20 @@ declare module 'vscode' { */ isPreferred?: boolean; + /** + * Marks that the code action cannot currently be applied. + * + * Disabled code actions will be surfaced in the refactor UI but cannot be applied. + */ + disabled?: { + /** + * Human readable description of why the code action is currently disabled. + * + * This is displayed in the code actions UI. + */ + readonly reason: string; + }; + /** * Creates a new code action. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ae24d42e99..72fe5ec72a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1358,26 +1358,6 @@ declare module 'vscode' { //#endregion - //#region Surfacing reasons why a code action cannot be applied to users: https://github.com/microsoft/vscode/issues/85160 - - export interface CodeAction { - /** - * Marks that the code action cannot currently be applied. - * - * Disabled code actions will be surfaced in the refactor UI but cannot be applied. - */ - disabled?: { - /** - * Human readable description of why the code action is currently disabled. - * - * This is displayed in the UI. - */ - reason: string; - }; - } - - //#endregion - //#region Allow theme icons in hovers: https://github.com/microsoft/vscode/issues/84695 export interface MarkdownString { @@ -1464,10 +1444,31 @@ declare module 'vscode' { //#region https://github.com/microsoft/vscode/issues/77728 + /** + * Additional data for entries of a workspace edit. Supports to label entries and marks entries + * as needing confirmation by the user. The editor groups edits with equal labels into tree nodes, + * for instance all edits labelled with "Changes in Strings" would be a tree node. + */ export interface WorkspaceEditMetadata { + + /** + * A flag which indicates that user confirmation is needed. + */ needsConfirmation: boolean; + + /** + * A human-readable string which is rendered prominent. + */ label: string; + + /** + * A human-readable string which is rendered less prominent in the same line. + */ description?: string; + + /** + * The icon path or [ThemeIcon](#ThemeIcon) for the edit. + */ iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; } @@ -1498,9 +1499,9 @@ declare module 'vscode' { value: string | number; /** - * A link to a URI with more information about the diagnostic error. + * A target URI to open with more information about the diagnostic error. */ - link: Uri; + target: Uri; } } @@ -1620,11 +1621,11 @@ declare module 'vscode' { * parallel and the results are merged. A failing provider (rejected promise or exception) will * not cause a failure of the whole operation. * - * @param selector A selector that defines the documents this provider is applicable to. + * @param scheme A scheme or schemes that defines which documents this provider is applicable to. Can be `*` to target all documents. * @param provider A timeline provider. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - export function registerTimelineProvider(selector: DocumentSelector, provider: TimelineProvider): Disposable; + export function registerTimelineProvider(scheme: string | string[], provider: TimelineProvider): Disposable; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadDiagnostics.ts b/src/vs/workbench/api/browser/mainThreadDiagnostics.ts index c4e650859f..9c548e3dcf 100644 --- a/src/vs/workbench/api/browser/mainThreadDiagnostics.ts +++ b/src/vs/workbench/api/browser/mainThreadDiagnostics.ts @@ -55,7 +55,7 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape { } } if (marker.code && typeof marker.code !== 'string') { - marker.code.link = URI.revive(marker.code.link); + marker.code.target = URI.revive(marker.code.target); } } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c7a944972b..5a63f66f36 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -770,9 +770,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables); }, - registerTimelineProvider: (scheme: string, provider: vscode.TimelineProvider) => { + registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => { checkProposedApiEnabled(extension); - return extHostTimeline.registerTimelineProvider(provider, extension.identifier, extHostCommands.converter); + return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter); } }; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 6f625ef1dc..db2898d4bb 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import * as modes from 'vs/editor/common/modes'; import { Emitter, Event } from 'vs/base/common/event'; import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -11,7 +11,7 @@ import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class AuthenticationProviderWrapper implements vscode.AuthenticationProvider { - onDidChangeSessions: Event; + readonly onDidChangeSessions: vscode.Event; constructor(private _requestingExtension: IExtensionDescription, private _provider: vscode.AuthenticationProvider, diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 6683355a70..2867b5fef1 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -198,7 +198,7 @@ export class ExtHostConfigProvider { }; return isObject(target) ? new Proxy(target, { - get: (target: any, property: string) => { + get: (target: any, property: PropertyKey) => { if (typeof property === 'string' && property.toLowerCase() === 'tojson') { cloneTarget(); return () => clonedTarget; @@ -213,21 +213,21 @@ export class ExtHostConfigProvider { } return result; }, - set: (_target: any, property: string, value: any) => { + set: (_target: any, property: PropertyKey, value: any) => { cloneTarget(); if (clonedTarget) { clonedTarget[property] = value; } return true; }, - deleteProperty: (_target: any, property: string) => { + deleteProperty: (_target: any, property: PropertyKey) => { cloneTarget(); if (clonedTarget) { delete clonedTarget[property]; } return true; }, - defineProperty: (_target: any, property: string, descriptor: any) => { + defineProperty: (_target: any, property: PropertyKey, descriptor: any) => { cloneTarget(); if (clonedTarget) { Object.defineProperty(clonedTarget, property, descriptor); @@ -284,10 +284,10 @@ export class ExtHostConfigProvider { const readonlyProxy = (target: any): any => { return isObject(target) ? new Proxy(target, { - get: (target: any, property: string) => readonlyProxy(target[property]), - set: (_target: any, property: string, _value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${property}' of object`); }, - deleteProperty: (_target: any, property: string) => { throw new Error(`TypeError: Cannot delete read only property '${property}' of object`); }, - defineProperty: (_target: any, property: string) => { throw new Error(`TypeError: Cannot define property '${property}' for a readonly object`); }, + get: (target: any, property: PropertyKey) => readonlyProxy(target[property]), + set: (_target: any, property: PropertyKey, _value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${String(property)}' of object`); }, + deleteProperty: (_target: any, property: PropertyKey) => { throw new Error(`TypeError: Cannot delete read only property '${String(property)}' of object`); }, + defineProperty: (_target: any, property: PropertyKey) => { throw new Error(`TypeError: Cannot define property '${String(property)}' for a readonly object`); }, setPrototypeOf: (_target: any) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); }, isExtensible: () => false, preventExtensions: () => true diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index 3f652d9671..daf5cef752 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -39,7 +39,7 @@ export class ExtHostTimeline implements IExtHostTimeline { return provider?.provideTimeline(URI.revive(uri), token) ?? []; } - registerTimelineProvider(provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { + registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { const timelineDisposables = new DisposableStore(); const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables); @@ -51,6 +51,7 @@ export class ExtHostTimeline implements IExtHostTimeline { return this.registerTimelineProviderCore({ ...provider, + scheme: scheme, onDidChange: undefined, async provideTimeline(uri: URI, token: CancellationToken) { timelineDisposables.clear(); @@ -116,7 +117,8 @@ export class ExtHostTimeline implements IExtHostTimeline { this._proxy.$registerTimelineProvider({ id: provider.id, - label: provider.label + label: provider.label, + scheme: provider.scheme }); this._providers.set(provider.id, provider); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 8c5e7fac76..c9b873009d 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -127,11 +127,11 @@ export namespace DiagnosticTag { export namespace Diagnostic { export function from(value: vscode.Diagnostic): IMarkerData { - let code: string | { value: string; link: URI } | undefined = isString(value.code) || isNumber(value.code) ? String(value.code) : undefined; + let code: string | { value: string; target: URI } | undefined = isString(value.code) || isNumber(value.code) ? String(value.code) : undefined; if (value.code2) { code = { value: String(value.code2.value), - link: value.code2.link + target: value.code2.target }; } @@ -157,7 +157,7 @@ export namespace Diagnostic { } export namespace DiagnosticRelatedInformation { - export function from(value: types.DiagnosticRelatedInformation): IRelatedInformation { + export function from(value: vscode.DiagnosticRelatedInformation): IRelatedInformation { return { ...Range.from(value.location.range), message: value.message, diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index d2aad3396f..3dbe39636d 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -28,6 +28,32 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' const registry = Registry.as(Extensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); +// --- Close Side Bar + +export class CloseSidebarAction extends Action { + + static readonly ID = 'workbench.action.closeSidebar'; + static readonly LABEL = nls.localize('closeSidebar', "Close Side Bar"); + + constructor( + id: string, + label: string, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + ) { + super(id, label); + + this.enabled = !!this.layoutService; + } + + run(): Promise { + this.layoutService.setSideBarHidden(true); + + return Promise.resolve(); + } +} + +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseSidebarAction, CloseSidebarAction.ID, CloseSidebarAction.LABEL), 'View: Close Side Bar ', viewCategory); + // --- Toggle Activity Bar export class ToggleActivityBarVisibilityAction extends Action { diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 0fe4c7bc1e..a7e549aa33 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -8,18 +8,20 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { IEditorGroupsService, GroupDirection, GroupLocation, IFindGroupScope } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IPanel } from 'vs/workbench/common/panel'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { Direction } from 'vs/base/browser/ui/grid/grid'; abstract class BaseNavigationAction extends Action { constructor( id: string, label: string, + protected direction: Direction, @IEditorGroupsService protected editorGroupService: IEditorGroupsService, @IPanelService protected panelService: IPanelService, @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, @@ -33,37 +35,40 @@ abstract class BaseNavigationAction extends Action { const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART); const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART); - const isSidebarPositionLeft = this.layoutService.getSideBarPosition() === PartPosition.LEFT; - const isPanelPositionDown = this.layoutService.getPanelPosition() === PartPosition.BOTTOM; - + let neighborPart: Parts | undefined; if (isEditorFocus) { - return this.navigateOnEditorFocus(isSidebarPositionLeft, isPanelPositionDown); + const didNavigate = this.navigateAcrossEditorGroup(this.toGroupDirection(this.direction)); + if (didNavigate) { + return Promise.resolve(true); + } + + neighborPart = this.layoutService.getVisibleNeighborPart(Parts.EDITOR_PART, this.direction); } if (isPanelFocus) { - return this.navigateOnPanelFocus(isSidebarPositionLeft, isPanelPositionDown); + neighborPart = this.layoutService.getVisibleNeighborPart(Parts.PANEL_PART, this.direction); } if (isSidebarFocus) { - return Promise.resolve(this.navigateOnSidebarFocus(isSidebarPositionLeft, isPanelPositionDown)); + neighborPart = this.layoutService.getVisibleNeighborPart(Parts.SIDEBAR_PART, this.direction); + } + + if (neighborPart === Parts.EDITOR_PART) { + return Promise.resolve(this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST)); + } + + if (neighborPart === Parts.SIDEBAR_PART) { + return this.navigateToSidebar(); + } + + if (neighborPart === Parts.PANEL_PART) { + return this.navigateToPanel(); } return Promise.resolve(false); } - protected navigateOnEditorFocus(_isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): Promise { - return Promise.resolve(true); - } - - protected navigateOnPanelFocus(_isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): Promise { - return Promise.resolve(true); - } - - protected navigateOnSidebarFocus(_isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): boolean | IViewlet { - return true; - } - - protected async navigateToPanel(): Promise { + private async navigateToPanel(): Promise { if (!this.layoutService.isVisible(Parts.PANEL_PART)) { return false; } @@ -83,7 +88,7 @@ abstract class BaseNavigationAction extends Action { return res; } - protected async navigateToSidebar(): Promise { + private async navigateToSidebar(): Promise { if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { return Promise.resolve(false); } @@ -98,14 +103,23 @@ abstract class BaseNavigationAction extends Action { return !!viewlet; } - protected navigateAcrossEditorGroup(direction: GroupDirection): boolean { + private navigateAcrossEditorGroup(direction: GroupDirection): boolean { return this.doNavigateToEditorGroup({ direction }); } - protected navigateToEditorGroup(location: GroupLocation): boolean { + private navigateToEditorGroup(location: GroupLocation): boolean { return this.doNavigateToEditorGroup({ location }); } + private toGroupDirection(direction: Direction): GroupDirection { + switch (direction) { + case Direction.Down: return GroupDirection.DOWN; + case Direction.Left: return GroupDirection.LEFT; + case Direction.Right: return GroupDirection.RIGHT; + case Direction.Up: return GroupDirection.UP; + } + } + private doNavigateToEditorGroup(scope: IFindGroupScope): boolean { const targetGroup = this.editorGroupService.findGroup(scope, this.editorGroupService.activeGroup); if (targetGroup) { @@ -131,40 +145,7 @@ class NavigateLeftAction extends BaseNavigationAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IViewletService viewletService: IViewletService ) { - super(id, label, editorGroupService, panelService, layoutService, viewletService); - } - - protected navigateOnEditorFocus(isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): Promise { - const didNavigate = this.navigateAcrossEditorGroup(GroupDirection.LEFT); - if (didNavigate) { - return Promise.resolve(true); - } - - if (isSidebarPositionLeft) { - return this.navigateToSidebar(); - } - - return Promise.resolve(false); - } - - protected navigateOnPanelFocus(isSidebarPositionLeft: boolean, isPanelPositionDown: boolean): Promise { - if (isPanelPositionDown && isSidebarPositionLeft) { - return this.navigateToSidebar(); - } - - if (!isPanelPositionDown) { - return Promise.resolve(this.navigateToEditorGroup(GroupLocation.LAST)); - } - - return Promise.resolve(false); - } - - protected navigateOnSidebarFocus(isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): boolean { - if (!isSidebarPositionLeft) { - return this.navigateToEditorGroup(GroupLocation.LAST); - } - - return false; + super(id, label, Direction.Left, editorGroupService, panelService, layoutService, viewletService); } } @@ -181,40 +162,7 @@ class NavigateRightAction extends BaseNavigationAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IViewletService viewletService: IViewletService ) { - super(id, label, editorGroupService, panelService, layoutService, viewletService); - } - - protected navigateOnEditorFocus(isSidebarPositionLeft: boolean, isPanelPositionDown: boolean): Promise { - const didNavigate = this.navigateAcrossEditorGroup(GroupDirection.RIGHT); - if (didNavigate) { - return Promise.resolve(true); - } - - if (!isPanelPositionDown) { - return this.navigateToPanel(); - } - - if (!isSidebarPositionLeft) { - return this.navigateToSidebar(); - } - - return Promise.resolve(false); - } - - protected navigateOnPanelFocus(isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): Promise { - if (!isSidebarPositionLeft) { - return this.navigateToSidebar(); - } - - return Promise.resolve(false); - } - - protected navigateOnSidebarFocus(isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): boolean { - if (isSidebarPositionLeft) { - return this.navigateToEditorGroup(GroupLocation.FIRST); - } - - return false; + super(id, label, Direction.Right, editorGroupService, panelService, layoutService, viewletService); } } @@ -231,19 +179,7 @@ class NavigateUpAction extends BaseNavigationAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IViewletService viewletService: IViewletService ) { - super(id, label, editorGroupService, panelService, layoutService, viewletService); - } - - protected navigateOnEditorFocus(_isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): Promise { - return Promise.resolve(this.navigateAcrossEditorGroup(GroupDirection.UP)); - } - - protected navigateOnPanelFocus(_isSidebarPositionLeft: boolean, isPanelPositionDown: boolean): Promise { - if (isPanelPositionDown) { - return Promise.resolve(this.navigateToEditorGroup(GroupLocation.LAST)); - } - - return Promise.resolve(false); + super(id, label, Direction.Up, editorGroupService, panelService, layoutService, viewletService); } } @@ -260,20 +196,7 @@ class NavigateDownAction extends BaseNavigationAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IViewletService viewletService: IViewletService ) { - super(id, label, editorGroupService, panelService, layoutService, viewletService); - } - - protected navigateOnEditorFocus(_isSidebarPositionLeft: boolean, isPanelPositionDown: boolean): Promise { - const didNavigate = this.navigateAcrossEditorGroup(GroupDirection.DOWN); - if (didNavigate) { - return Promise.resolve(true); - } - - if (isPanelPositionDown) { - return this.navigateToPanel(); - } - - return Promise.resolve(false); + super(id, label, Direction.Down, editorGroupService, panelService, layoutService, viewletService); } } diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 97a5685c74..e59fe0f6e1 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -361,14 +361,20 @@ class ResourceLabelWidget extends IconLabel { // provided. If they are not provided from the label we got // we assume that the client does not want to display them // and as such do not override. - const untitledEditor = this.textFileService.untitled.get(label.resource); - if (untitledEditor && !untitledEditor.hasAssociatedFilePath) { + const untitledModel = this.textFileService.untitled.get(label.resource); + if (untitledModel && !untitledModel.hasAssociatedFilePath) { if (typeof label.name === 'string') { - label.name = untitledEditor.getName(); + label.name = untitledModel.name; } if (typeof label.description === 'string') { - const untitledDescription = untitledEditor.getDescription(); + let untitledDescription: string; + if (untitledModel.hasAssociatedFilePath) { + untitledDescription = this.labelService.getUriLabel(resources.dirname(untitledModel.resource), { relative: true }); + } else { + untitledDescription = untitledModel.resource.path; + } + if (label.name !== untitledDescription) { label.description = untitledDescription; } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 4a49bd7743..96edafb304 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1265,6 +1265,35 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onMaximizeChange.fire(maximized); } + getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined { + if (!this.workbenchGrid) { + return undefined; + } + + if (!this.isVisible(part)) { + return undefined; + } + + const neighborViews = this.workbenchGrid.getNeighborViews(this.getPart(part), direction, false); + + if (!neighborViews) { + return undefined; + } + + for (const neighborView of neighborViews) { + const neighborPart = + [Parts.ACTIVITYBAR_PART, Parts.EDITOR_PART, Parts.PANEL_PART, Parts.SIDEBAR_PART, Parts.STATUSBAR_PART, Parts.TITLEBAR_PART] + .find(partId => this.getPart(partId) === neighborView && this.isVisible(partId)); + + if (neighborPart !== undefined) { + return neighborPart; + } + } + + return undefined; + } + + private arrangeEditorNodes(editorNode: ISerializedNode, panelNode: ISerializedNode, editorSectionWidth: number): ISerializedNode[] { switch (this.state.panel.position) { case Position.BOTTOM: diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index e1d4c8be62..21eacce38d 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -17,7 +17,7 @@ import 'vs/css!./media/breadcrumbscontrol'; import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; -import { IConstructorSignature1, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchDataTree, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { breadcrumbsPickerBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -30,11 +30,9 @@ import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, O import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker { - const ctor: IConstructorSignature1 = element instanceof FileElement - ? BreadcrumbsFilePicker - : BreadcrumbsOutlinePicker; - - return instantiationService.createInstance(ctor, parent); + return element instanceof FileElement + ? instantiationService.createInstance(BreadcrumbsFilePicker, parent) + : instantiationService.createInstance(BreadcrumbsOutlinePicker, parent); } interface ILayoutInfo { @@ -375,7 +373,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { const labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */); this._disposables.add(labels); - return this._instantiationService.createInstance>(WorkbenchAsyncDataTree, 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { + return >this._instantiationService.createInstance(WorkbenchAsyncDataTree, 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { multipleSelectionSupport: false, sorter: new FileSorter(), filter: this._instantiationService.createInstance(FileFilter), @@ -444,7 +442,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { } protected _createTree(container: HTMLElement) { - return this._instantiationService.createInstance>( + return >this._instantiationService.createInstance( WorkbenchDataTree, 'BreadcrumbsOutlinePicker', container, diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 7396cd96f2..03aa9cbb8a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -13,7 +13,7 @@ import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFa import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 19cdf5abf4..e8c39396e8 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -12,7 +12,7 @@ import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/ import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; import { Disposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorAction } from 'vs/editor/common/editorCommon'; @@ -1013,7 +1013,7 @@ export class ChangeModeAction extends Action { const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; let hasLanguageSupport = !!resource; - if (resource?.scheme === Schemas.untitled && !this.textFileService.untitled.hasAssociatedFilePath(resource)) { + if (resource?.scheme === Schemas.untitled && !this.textFileService.untitled.get(resource)?.hasAssociatedFilePath) { hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1") } diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 346d0e8fdb..180677f44f 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -9,7 +9,7 @@ import { ICodeEditor, getCodeEditor, IPasteEvent } from 'vs/editor/browser/edito import { TextEditorOptions, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 47bdea2b89..b406950c46 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -73,7 +73,7 @@ export class NotificationsList extends Themable { const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner); // List - const list = this.list = this._register(this.instantiationService.createInstance>( + const list = this.list = >this._register(this.instantiationService.createInstance( WorkbenchList, 'NotificationsList', this.listContainer, diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index c7a943d55b..73cfb24577 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -150,9 +150,9 @@ function createPositionPanelActionConfig(id: string, alias: string, label: strin } export const PositionPanelActionConfigs: PanelActionConfig[] = [ - createPositionPanelActionConfig(PositionPanelActionId.LEFT, 'View: Panel Position Left', nls.localize('positionPanelLeft', 'Move Panel Left'), Position.LEFT), - createPositionPanelActionConfig(PositionPanelActionId.RIGHT, 'View: Panel Position Right', nls.localize('positionPanelRight', 'Move Panel Right'), Position.RIGHT), - createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, 'View: Panel Position Bottom', nls.localize('positionPanelBottom', 'Move Panel To Bottom'), Position.BOTTOM), + createPositionPanelActionConfig(PositionPanelActionId.LEFT, 'View: Move Panel Left', nls.localize('positionPanelLeft', 'Move Panel Left'), Position.LEFT), + createPositionPanelActionConfig(PositionPanelActionId.RIGHT, 'View: Move Panel Right', nls.localize('positionPanelRight', 'Move Panel Right'), Position.RIGHT), + createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, 'View: Move Panel To Bottom', nls.localize('positionPanelBottom', 'Move Panel To Bottom'), Position.BOTTOM), ]; const positionByActionId = new Map(PositionPanelActionConfigs.map(config => [config.id, config.value])); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 8c698f944d..7dab812e5a 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -61,7 +61,7 @@ export abstract class MenubarControl extends Disposable { // 'Selection': IMenu; {{SQL CARBON EDIT}} - Disable unusued menus 'View': IMenu; // 'Go': IMenu; {{SQL CARBON EDIT}} - Disable unusued menus - // 'Debug': IMenu; {{SQL CARBON EDIT}} - Disable unusued menus + // 'Run': IMenu; {{SQL CARBON EDIT}} - Disable unusued menus // 'Terminal': IMenu; {{SQL CARBON EDIT}} - Disable unusued menus 'Window'?: IMenu; 'Help': IMenu; @@ -73,9 +73,9 @@ export abstract class MenubarControl extends Disposable { 'Edit': nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit"), // 'Selection': nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"), {{SQL CARBON EDIT}} - Disable unused menus 'View': nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"), - // 'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"), {{SQL CARBON EDIT}} - Disable unused menus - // 'Debug': nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), {{SQL CARBON EDIT}} - Disable unused menus - // 'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), {{SQL CARBON EDIT}} - Disable unused menus + // 'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"), {{SQL CARBON EDIT}} - Disable unusued menus + // 'Run': nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run"), {{SQL CARBON EDIT}} - Disable unusued menus + // 'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), {{SQL CARBON EDIT}} - Disable unusued menus 'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") }; @@ -110,7 +110,7 @@ export abstract class MenubarControl extends Disposable { // 'Selection': this._register(this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService)), 'View': this._register(this.menuService.createMenu(MenuId.MenubarViewMenu, this.contextKeyService)), // 'Go': this._register(this.menuService.createMenu(MenuId.MenubarGoMenu, this.contextKeyService)), - // 'Debug': this._register(this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService)), + // 'Run': this._register(this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService)), // 'Terminal': this._register(this.menuService.createMenu(MenuId.MenubarTerminalMenu, this.contextKeyService)), 'Help': this._register(this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService)) }; diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 33fd164612..952714c66c 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -43,10 +43,10 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry private readonly toBeInstantiated: Map[]> = new Map[]>(); - registerWorkbenchContribution(ctor: { new(...services: Services): IWorkbenchContribution }, phase: LifecyclePhase = LifecyclePhase.Starting): void { + registerWorkbenchContribution(ctor: new (...services: Services) => IWorkbenchContribution, phase: LifecyclePhase = LifecyclePhase.Starting): void { // Instantiate directly if we are already matching the provided phase if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) { - this.instantiationService.createInstance(ctor); + this.instantiationService.createInstance(ctor); } // Otherwise keep contributions by lifecycle phase @@ -57,7 +57,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry this.toBeInstantiated.set(phase, toBeInstantiated); } - toBeInstantiated.push(ctor); + toBeInstantiated.push(ctor as IConstructorSignature0); } } diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index c35ca6df45..3c678d14fc 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -77,6 +77,10 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel this.modelService.setMode(this.textEditorModel, this.modeService.create(mode)); } + getMode(): string | undefined { + return this.textEditorModel?.getModeId(); + } + /** * Creates the text editor model with the provided value, optional preferred mode * (can be comma separated for multiple values) and optional resource URL. diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 1b56b4f341..6782e99b8f 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -26,6 +26,8 @@ import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/act import { IEditorInput } from 'vs/workbench/common/editor'; import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import Severity from 'vs/base/common/severity'; async function getBulkEditPane(viewsService: IViewsService): Promise { const view = await viewsService.openView(BulkEditPane.ID, true); @@ -98,6 +100,7 @@ class BulkEditPreviewContribution { @IPanelService private readonly _panelService: IPanelService, @IViewsService private readonly _viewsService: IViewsService, @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, + @IDialogService private readonly _dialogService: IDialogService, @IBulkEditService bulkEditService: IBulkEditService, @IContextKeyService contextKeyService: IContextKeyService, ) { @@ -108,6 +111,27 @@ class BulkEditPreviewContribution { private async _previewEdit(edit: WorkspaceEdit) { this._ctxEnabled.set(true); + const view = await getBulkEditPane(this._viewsService); + if (!view) { + this._ctxEnabled.set(false); + return edit; + } + + // check for active preview session and let the user decide + if (view.hasInput()) { + const choice = await this._dialogService.show( + Severity.Info, + localize('overlap', "Another refactoring is being previewed."), + [localize('cancel', "Cancel"), localize('continue', "Continue")], + { detail: localize('detail', "Press 'Continue' to discard the previous refactoring and continue with the current refactoring.") } + ); + + if (choice.choice === 0) { + // this refactoring is being cancelled + return { edits: [] }; + } + } + // session let session: PreviewSession; if (this._activeSession) { @@ -120,10 +144,6 @@ class BulkEditPreviewContribution { // the actual work... try { - const view = await getBulkEditPane(this._viewsService); - if (!view) { - return edit; - } const newEditOrUndefined = await view.setInput(edit, session.cts.token); if (!newEditOrUndefined) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index f0a2be6b6c..ecdfd19492 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -119,7 +119,7 @@ export class BulkEditPane extends ViewPane { this._treeDataSource.groupByFile = this._storageService.getBoolean(BulkEditPane._memGroupByFile, StorageScope.GLOBAL, true); this._ctxGroupByFile.set(this._treeDataSource.groupByFile); - this._tree = this._instaService.createInstance( + this._tree = >this._instaService.createInstance( WorkbenchAsyncDataTree, this.id, treeContainer, new BulkEditDelegate(), [new TextEditElementRenderer(), this._instaService.createInstance(FileElementRenderer, resourceLabels), new CategoryElementRenderer()], @@ -196,6 +196,10 @@ export class BulkEditPane extends ViewPane { }); } + hasInput(): boolean { + return Boolean(this._currentInput); + } + private async _setTreeInput(input: BulkFileOperations) { const viewState = this._treeViewStates.get(this._treeDataSource.groupByFile); @@ -243,6 +247,15 @@ export class BulkEditPane extends ViewPane { this._done(false); } + private _done(accept: boolean): void { + if (this._currentResolve) { + this._currentResolve(accept ? this._currentInput?.getWorkspaceEdit() : undefined); + } + this._currentInput = undefined; + this._setState(State.Message); + this._sessionDisposables.clear(); + } + toggleChecked() { const [first] = this._tree.getFocus(); if ((first instanceof FileElement || first instanceof TextEditElement) && !first.isDisabled()) { @@ -280,15 +293,6 @@ export class BulkEditPane extends ViewPane { } } - private _done(accept: boolean): void { - if (this._currentResolve) { - this._currentResolve(accept ? this._currentInput?.getWorkspaceEdit() : undefined); - this._currentInput = undefined; - } - this._setState(State.Message); - this._sessionDisposables.clear(); - } - private async _openElementAsEditor(e: IOpenEvent): Promise { type Mutable = { -readonly [P in keyof T]: T[P] diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index d3086e820b..65bf2db355 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -287,10 +287,6 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { result += `language${escape(tmMetadata?.languageIdentifier.language || '')}`; result += `standard token type${this._tokenTypeToString(tmMetadata?.tokenType || StandardTokenType.Other)}`; - result += ``; - - result += ``; - result += ``; result += this._formatMetadata(semMetadata, tmMetadata); result += ``; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 7c2bb94b20..d768edbbd8 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -81,7 +81,7 @@ export class BreakpointsView extends ViewPane { dom.addClass(container, 'debug-breakpoints'); const delegate = new BreakpointsDelegate(this.debugService); - this.list = this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ + this.list = >this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ this.instantiationService.createInstance(BreakpointsRenderer), new ExceptionBreakpointsRenderer(this.debugService), this.instantiationService.createInstance(FunctionBreakpointsRenderer), diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 5eeec2b052..5c86a5953a 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -155,7 +155,7 @@ export class CallStackView extends ViewPane { const treeContainer = renderViewTree(container); this.dataSource = new CallStackDataSource(this.debugService); - this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), [ + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), [ new SessionsRenderer(this.instantiationService, this.debugService), new ThreadsRenderer(this.instantiationService), this.instantiationService.createInstance(StackFramesRenderer), diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 3447ca2498..8e4f81a447 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -70,7 +70,7 @@ class OpenDebugViewletAction extends ShowViewletAction { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, - name: nls.localize('runAndDebug', "Run and Debug"), + name: nls.localize('run', "Run"), ctorDescriptor: new SyncDescriptor(DebugViewPaneContainer), icon: 'codicon-debug-alt', order: 13 // {{SQL CARBON EDIT}} @@ -128,7 +128,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(StartAction, StartA registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Start Without Debugging', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Run (Start Without Debugging)', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); @@ -341,27 +341,23 @@ registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorCo // View menu -// {{SQL CARBON EDIT}} - Disable unused menu item -// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { -// group: '3_views', -// command: { -// id: VIEWLET_ID, -// title: nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug") -// }, -// order: 4 -// }); -// {{SQL CARBON EDIT}} - End +/*MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '3_views', + command: { + id: VIEWLET_ID, + title: nls.localize({ key: 'miViewRun', comment: ['&& denotes a mnemonic'] }, "&&Run") + }, + order: 4 +}); -// {{SQL CARBON EDIT}} - Disable unused menu item -// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { -// group: '4_panels', -// command: { -// id: OpenDebugPanelAction.ID, -// title: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console") -// }, -// order: 2 -// }); -// {{SQL CARBON EDIT}} - End +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '4_panels', + command: { + id: OpenDebugPanelAction.ID, + title: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console") + }, + order: 2 +}); {{SQL CARBON EDIT}} - Disable unusued menus */ // Debug menu diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index c865475423..b800933ce9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -101,7 +101,7 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer.setAttribute('role', 'tree'); const dataSource = new DebugHoverDataSource(); - this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], dataSource, { ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"), accessibilityProvider: new DebugHoverAccessibilityProvider(), diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 73e866224f..0e788a79f4 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -442,7 +442,7 @@ export class LoadedScriptsView extends ViewPane { this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.treeLabels); - this.tree = this.instantiationService.createInstance(WorkbenchCompressibleObjectTree, + this.tree = >this.instantiationService.createInstance(WorkbenchCompressibleObjectTree, 'LoadedScriptsView', this.treeContainer, new LoadedScriptsDelegate(), diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 64d5be6933..7a11c460ba 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -439,7 +439,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { const wordWrap = this.configurationService.getValue('debug').console.wordWrap; dom.toggleClass(treeContainer, 'word-wrap', wordWrap); const linkDetector = this.instantiationService.createInstance(LinkDetector); - this.tree = this.instantiationService.createInstance>( + this.tree = >this.instantiationService.createInstance( WorkbenchAsyncDataTree, 'DebugRepl', treeContainer, diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index e7dfaaf201..08cf9d5c2f 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -88,7 +88,7 @@ export class VariablesView extends ViewPane { dom.addClass(container, 'debug-variables'); const treeContainer = renderViewTree(container); - this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), [this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()], new VariablesDataSource(), { ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"), diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index daab291f90..556eead50f 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -66,7 +66,7 @@ export class WatchExpressionsView extends ViewPane { const treeContainer = renderViewTree(container); const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer); - this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)], + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)], new WatchExpressionsDataSource(), { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"), accessibilityProvider: new WatchExpressionsAccessibilityProvider(), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 5446d50150..44c70fb1a5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1950,7 +1950,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureWorkspaceRecommendedExtensionsAction.ID, - title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceRecommendedExtensionsAction.LABEL}`, original: 'Extensions: Configure Recommended Extensions (Workspace)' }, + title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceRecommendedExtensionsAction.LABEL}`, original: 'Configure Recommended Extensions (Workspace)' }, category: localize('extensions', "Extensions") }, when: this.workspaceContextKey @@ -1962,7 +1962,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, - title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL}`, original: 'Extensions: Configure Recommended Extensions (Workspace Folder)' }, + title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL}`, original: 'Configure Recommended Extensions (Workspace Folder)' }, category: localize('extensions', "Extensions") }, when: this.workspaceFolderContextKey @@ -1976,7 +1976,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceRecommendationsAction.ADD_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.ADD_LABEL}`, original: 'Extensions: Add to Recommended Extensions (Workspace)' }, + title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.ADD_LABEL}`, original: 'Add to Recommended Extensions (Workspace)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceRecommendationsContextKey diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 55ab74f10d..408c8942b1 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -49,6 +49,7 @@ } .extension-editor > .header > .icon-container .extension-remote-badge .codicon { + color: currentColor; font-size: 28px; } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index e6085d7ed7..1d2205deaf 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -133,6 +133,10 @@ justify-content: center; } +.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container .extension-remote-badge .codicon { + color: currentColor; +} + .extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header > .extension-remote-badge-container { margin-left: 6px; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 120fde9bea..b82b12a8fa 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -407,7 +407,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { } }; - this._list = this._instantiationService.createInstance>(WorkbenchList, + this._list = >this._instantiationService.createInstance(WorkbenchList, 'RuntimeExtensions', parent, delegate, [renderer], { multipleSelectionSupport: false, diff --git a/src/vs/workbench/contrib/externalTerminal/test/electron-browser/externalTerminalService.test.ts b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts similarity index 100% rename from src/vs/workbench/contrib/externalTerminal/test/electron-browser/externalTerminalService.test.ts rename to src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index e37cdbb363..940dfb6495 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -364,7 +364,7 @@ export class ExplorerView extends ViewPane { const isCompressionEnabled = () => this.configurationService.getValue('explorer.compactFolders'); - this.tree = this.instantiationService.createInstance>(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer], + this.tree = >this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer], this.instantiationService.createInstance(ExplorerDataSource), { compressionEnabled: isCompressionEnabled(), accessibilityProvider: this.renderer, diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 45d893e8c4..800d02159c 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -214,7 +214,7 @@ export class OpenEditorsView extends ViewPane { this.listLabels.clear(); } this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); - this.list = this.instantiationService.createInstance(WorkbenchList, 'OpenEditors', container, delegate, [ + this.list = >this.instantiationService.createInstance(WorkbenchList, 'OpenEditors', container, delegate, [ new EditorGroupRenderer(this.keybindingService, this.instantiationService), new OpenEditorRenderer(this.listLabels, this.instantiationService, this.keybindingService, this.configurationService) ], { diff --git a/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts index 170bd1d8b8..952b1f9a19 100644 --- a/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts @@ -33,7 +33,7 @@ async function getRenderer( renderer.code = (_code, lang) => { const modeId = modeService.getModeIdForLanguageName(lang); if (modeId) { - result.push(extensionService.whenInstalledExtensionsRegistered().then(() => { + result.push(extensionService.whenInstalledExtensionsRegistered().then((): PromiseLike | null => { modeService.triggerMode(modeId); return TokenizationRegistry.getPromise(modeId); })); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 24ed117dc0..4b3229b060 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -369,7 +369,7 @@ class MarkerWidget extends Disposable { this._codeLink = dom.$('a.code-link'); this._codeLink.setAttribute('title', this._getCodelinkTooltip()); - const codeUri = marker.code.link; + const codeUri = marker.code.target; const codeLink = codeUri.toString(); dom.append(parent, this._codeLink); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index c270e3615b..4347f091b6 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -316,7 +316,7 @@ export class OutlinePane extends ViewPane { this._treeDataSource = new OutlineDataSource(); this._treeComparator = new OutlineItemComparator(this._outlineViewState.sortBy); this._treeFilter = this._instantiationService.createInstance(OutlineFilter, 'outline'); - this._tree = this._instantiationService.createInstance( + this._tree = >this._instantiationService.createInstance( WorkbenchDataTree, 'OutlinePane', treeContainer, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index aa7b2b0079..66467ef2fe 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -49,6 +49,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingsEditorOptions, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; function createGroupIterator(group: SettingsTreeGroupElement): Iterator> { const groupsIt = Iterator.fromArray(group.children); @@ -456,7 +457,7 @@ export class SettingsEditor2 extends BaseEditor { const actionBar = this._register(new ActionBar(this.controlsElement, { animated: false, - actionViewItemProvider: (action: Action) => { return undefined; } + actionViewItemProvider: (_action) => { return undefined; } })); actionBar.push([clearInputAction], { label: false, icon: true }); @@ -890,8 +891,8 @@ export class SettingsEditor2 extends BaseEditor { private render(token: CancellationToken): Promise { if (this.input) { return this.input.resolve() - .then((model: Settings2EditorModel) => { - if (token.isCancellationRequested) { + .then((model: IEditorModel | null) => { + if (token.isCancellationRequested || !(model instanceof Settings2EditorModel)) { return undefined; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 3421488853..caa4c437a3 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -49,6 +49,7 @@ import { ExcludeSettingWidget, IListChangeEvent, IListDataItem, ListSettingWidge import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; +import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; const $ = DOM.$; @@ -1191,7 +1192,7 @@ export class SettingTreeRenderers { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextViewService private readonly _contextViewService: IContextViewService, - @IConfigurationService private readonly _configService: IConfigurationService, + @IUserDataSyncEnablementService private readonly _userDataSyncEnablementService: IUserDataSyncEnablementService, ) { this.settingActions = [ new Action('settings.resetSetting', localize('resetSettingLabel', "Reset Setting"), undefined, undefined, (context: SettingsTreeSettingElement) => { @@ -1235,7 +1236,7 @@ export class SettingTreeRenderers { } private getActionsForSetting(setting: ISetting): IAction[] { - const enableSync = this._configService.getValue('sync.enable'); + const enableSync = this._userDataSyncEnablementService.isEnabled(); return enableSync ? [this._instantiationService.createInstance(StopSyncingSettingAction, setting)] : []; diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 661560732b..934dc021a0 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -1063,7 +1063,7 @@ export class DirtyDiffModel extends Disposable { return this.diffDelayer .trigger(() => this.diff()) - .then((changes: IChange[]) => { + .then((changes: IChange[] | null) => { if (!this._editorModel || this._editorModel.isDisposed() || !this._originalModel || this._originalModel.isDisposed()) { return; // disposed } @@ -1072,6 +1072,10 @@ export class DirtyDiffModel extends Disposable { changes = []; } + if (!changes) { + changes = []; + } + const diff = sortedDiff(this._changes, changes, compareChanges); this._changes = changes; this._onDidChange.fire({ changes, diff }); diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index 5fb041384a..977392163e 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -196,7 +196,7 @@ export class MainPane extends ViewPane { const renderer = this.instantiationService.createInstance(ProviderRenderer); const identityProvider = { getId: (r: ISCMRepository) => r.provider.id }; - this.list = this.instantiationService.createInstance>(WorkbenchList, `SCM Main`, container, delegate, [renderer], { + this.list = >this.instantiationService.createInstance(WorkbenchList, `SCM Main`, container, delegate, [renderer], { identityProvider, horizontalScrolling: false, overrideStyles: { diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index c9a8ad5d35..a3e812745a 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -825,7 +825,7 @@ export class RepositoryPane extends ViewPane { const keyboardNavigationLabelProvider = new SCMTreeKeyboardNavigationLabelProvider(); const identityProvider = new SCMResourceIdentityProvider(); - this.tree = this.instantiationService.createInstance>( + this.tree = >this.instantiationService.createInstance( WorkbenchCompressibleObjectTree, 'SCM Tree Repo', this.listContainer, diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 72d2d13878..381074ff9a 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -51,7 +51,7 @@ import { VIEWLET_ID, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/sear import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { assertType } from 'vs/base/common/types'; +import { assertType, assertIsDefined } from 'vs/base/common/types'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import product from 'vs/platform/product/common/product'; @@ -68,19 +68,15 @@ const category = nls.localize('search', "Search"); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'workbench.action.search.toggleQueryDetails', weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.or(Constants.SearchViewVisibleKey, SearchEditorConstants.InSearchEditor), + when: ContextKeyExpr.or(Constants.SearchViewFocusedKey, SearchEditorConstants.InSearchEditor), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_J, handler: accessor => { - const editorService = accessor.get(IEditorService); - const contextService = accessor.get(IContextKeyService); - const control = editorService.activeControl; - if (control instanceof SearchEditor && !Constants.SearchViewFocusedKey.getValue(contextService)) { - control.toggleQueryDetails(); - } else { + const contextService = accessor.get(IContextKeyService).getContext(document.activeElement); + if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { + (accessor.get(IEditorService).activeControl as SearchEditor).toggleQueryDetails(); + } else if (contextService.getValue(Constants.SearchViewFocusedKey.serialize())) { const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - searchView.toggleQueryDetails(); - } + assertIsDefined(searchView).toggleQueryDetails(); } } }); @@ -577,21 +573,21 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ id: Constants.ToggleCaseSensitiveCommandId, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchViewFocusedKey, Constants.FileMatchOrFolderMatchFocusKey.toNegated()), + when: ContextKeyExpr.and(Constants.SearchViewFocusedKey, Constants.FileMatchOrFolderMatchFocusKey.toNegated()), handler: toggleCaseSensitiveCommand }, ToggleCaseSensitiveKeybinding)); KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ id: Constants.ToggleWholeWordCommandId, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchViewFocusedKey), + when: Constants.SearchViewFocusedKey, handler: toggleWholeWordCommand }, ToggleWholeWordKeybinding)); KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ id: Constants.ToggleRegexCommandId, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchViewFocusedKey), + when: Constants.SearchViewFocusedKey, handler: toggleRegexCommand }, ToggleRegexKeybinding)); diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index ef8561663f..f4d386d0e8 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -369,7 +369,7 @@ export class SearchDND implements ITreeDragAndDrop { onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { const elements = (data as ElementsDragAndDropData).elements; const resources: URI[] = elements - .filter(e => e instanceof FileMatch) + .filter((e): e is FileMatch => e instanceof FileMatch) .map((fm: FileMatch) => fm.resource); if (resources.length) { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index e47c12f2f6..e14c5d8fbf 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -66,6 +66,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Color, RGBA } from 'vs/base/common/color'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { OpenSearchEditorAction, createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; const $ = dom.$; @@ -97,7 +98,6 @@ export class SearchView extends ViewPane { private memento: Memento; private viewletVisible: IContextKey; - private viewletFocused: IContextKey; private inputBoxFocused: IContextKey; private inputPatternIncludesFocused: IContextKey; private inputPatternExclusionsFocused: IContextKey; @@ -158,7 +158,7 @@ export class SearchView extends ViewPane { @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ISearchWorkbenchService private readonly searchWorkbenchService: ISearchWorkbenchService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService readonly contextKeyService: IContextKeyService, @IReplaceService private readonly replaceService: IReplaceService, @ITextFileService private readonly textFileService: ITextFileService, @IPreferencesService private readonly preferencesService: IPreferencesService, @@ -173,8 +173,16 @@ export class SearchView extends ViewPane { super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); + this.container = dom.$('.search-view'); + + this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container)); + const viewletFocused = Constants.SearchViewFocusedKey.bindTo(this.contextKeyService); + viewletFocused.set(true); + + this.instantiationService = this.instantiationService.createChild( + new ServiceCollection([IContextKeyService, this.contextKeyService])); + this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService); - this.viewletFocused = Constants.SearchViewFocusedKey.bindTo(contextKeyService); this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService); this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService); this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService); @@ -342,9 +350,6 @@ export class SearchView extends ViewPane { this._register(this.searchWidget.searchInput.onInput(() => this.updateActions())); this._register(this.searchWidget.replaceInput.onInput(() => this.updateActions())); - this._register(this.onDidFocus(() => this.viewletFocused.set(true))); - this._register(this.onDidBlur(() => this.viewletFocused.set(false))); - this._register(this.onDidChangeBodyVisibility(visible => this.onVisibilityChanged(visible))); } @@ -696,7 +701,7 @@ export class SearchView extends ViewPane { }; this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility })); - this.tree = this._register(this.instantiationService.createInstance(WorkbenchObjectTree, + this.tree = this._register(>this.instantiationService.createInstance(WorkbenchObjectTree, 'SearchView', this.resultsElement, delegate, diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 5e95c0343f..17a3b103a3 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -174,11 +174,11 @@ registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey, SearchEditorConstants.EnableSearchEditorPreview)), - 'Search: Open Results in Editor', category, + 'Search Editor: Open Results in Editor', category, ContextKeyExpr.and(SearchEditorConstants.EnableSearchEditorPreview)); registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), - 'Search: Open New Search Editor', category, + 'Search Editor: Open New Search Editor', category, ContextKeyExpr.and(SearchEditorConstants.EnableSearchEditorPreview)); //#endregion diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index a2bc23e801..bc0a060664 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -43,6 +43,7 @@ import { InSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/consta import type { SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { extractSearchQuery, serializeSearchConfiguration, serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -68,6 +69,7 @@ export class SearchEditor extends BaseEditor { private searchOperation: LongRunningOperation; private searchHistoryDelayer: Delayer; private messageDisposables: IDisposable[] = []; + private container: HTMLElement; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -77,24 +79,30 @@ export class SearchEditor extends BaseEditor { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ILabelService private readonly labelService: ILabelService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService readonly instantiationService: IInstantiationService, @IContextViewService private readonly contextViewService: IContextViewService, @ICommandService private readonly commandService: ICommandService, @IContextKeyService readonly contextKeyService: IContextKeyService, @IEditorProgressService readonly progressService: IEditorProgressService, ) { super(SearchEditor.ID, telemetryService, themeService, storageService); - this.inSearchEditorContextKey = InSearchEditor.bindTo(contextKeyService); - this.inputFocusContextKey = InputBoxFocusedKey.bindTo(contextKeyService); + this.container = DOM.$('.search-editor'); + + const scopedContextKeyService = contextKeyService.createScoped(this.container); + this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + + this.inSearchEditorContextKey = InSearchEditor.bindTo(scopedContextKeyService); + this.inSearchEditorContextKey.set(true); + this.inputFocusContextKey = InputBoxFocusedKey.bindTo(scopedContextKeyService); this.searchOperation = this._register(new LongRunningOperation(progressService)); this.searchHistoryDelayer = new Delayer(2000); } createEditor(parent: HTMLElement) { - DOM.addClass(parent, 'search-editor'); + DOM.append(parent, this.container); - this.createQueryEditor(parent); - this.createResultsEditor(parent); + this.createQueryEditor(this.container); + this.createResultsEditor(this.container); } private createQueryEditor(parent: HTMLElement) { @@ -406,7 +414,6 @@ export class SearchEditor extends BaseEditor { this.saveViewState(); await super.setInput(newInput, options, token); - this.inSearchEditorContextKey.set(true); const { body, header } = await newInput.getModels(); @@ -478,7 +485,6 @@ export class SearchEditor extends BaseEditor { clearInput() { this.saveViewState(); super.clearInput(); - this.inSearchEditorContextKey.set(false); } } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index dc5bcca406..0a8a16db3d 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -2097,10 +2097,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const progressManager = this._providerProgressManager; const progressTimeout = setTimeout(() => { if (progressManager) { - progressManager.showProgress = (stillProviding) => { + progressManager.showProgress = (stillProviding, total) => { let message = undefined; if (stillProviding.length > 0) { - message = nls.localize('pickProgressManager.description', 'Getting tasks from extensions. {0} extension(s) remaining: {1}', stillProviding.length, stillProviding.join(', ')); + message = nls.localize('pickProgressManager.description', 'Detecting tasks ({0} of {1}): {2} in progress', total - stillProviding.length, total, stillProviding.join(', ')); } picker.description = message; }; @@ -2109,7 +2109,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer picker.customButton = false; }); if (!progressManager.isDone) { - picker.customLabel = nls.localize('taskQuickPick.cancel', "Cancel Remaining Extensions"); + picker.customLabel = nls.localize('taskQuickPick.cancel', "Stop detecting"); picker.onDidCustom(() => { this._providerProgressManager?.cancel(); }); diff --git a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts b/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts index d4c63679c2..2eb181665a 100644 --- a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts +++ b/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts @@ -11,9 +11,10 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; export class ProviderProgressMananger extends Disposable { private _onProviderComplete: Emitter = new Emitter(); private _stillProviding: Set = new Set(); + private _totalProviders: number = 0; private _onDone: Emitter = new Emitter(); private _isDone: boolean = false; - private _showProgress: ((remaining: string[]) => void) | undefined; + private _showProgress: ((remaining: string[], total: number) => void) | undefined; public canceled: CancellationTokenSource = new CancellationTokenSource(); constructor() { @@ -25,12 +26,13 @@ export class ProviderProgressMananger extends Disposable { this._onDone.fire(); } if (this._showProgress) { - this._showProgress(Array.from(this._stillProviding)); + this._showProgress(Array.from(this._stillProviding), this._totalProviders); } })); } public addProvider(taskType: string, provider: Promise) { + this._totalProviders++; this._stillProviding.add(taskType); provider.then(() => this._onProviderComplete.fire(taskType)); } @@ -39,9 +41,9 @@ export class ProviderProgressMananger extends Disposable { this._register(this._onDone.event(onDoneListener)); } - set showProgress(progressDisplayFunction: (remaining: string[]) => void) { + set showProgress(progressDisplayFunction: (remaining: string[], total: number) => void) { this._showProgress = progressDisplayFunction; - this._showProgress(Array.from(this._stillProviding)); + this._showProgress(Array.from(this._stillProviding), this._totalProviders); } get isDone(): boolean { @@ -51,7 +53,7 @@ export class ProviderProgressMananger extends Disposable { public cancel() { this._isDone = true; if (this._showProgress) { - this._showProgress([]); + this._showProgress([], 0); } this._onDone.fire(); this.canceled.cancel(); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 3928ab7a81..0e11cb6927 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -27,7 +27,7 @@ import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { IShellLaunchConfig, TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITerminalService, ITerminalInstanceService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; @@ -289,7 +289,7 @@ export class TerminalTaskSystem implements ITaskSystem { return false; } const activeTerminalInstance = this.terminalService.getActiveInstance(); - const isPanelShowingTerminal = this.panelService.getActivePanel()?.getId() === TERMINAL_PANEL_ID; + const isPanelShowingTerminal = !!this.viewsService.getActiveViewWithId(TERMINAL_VIEW_ID); return isPanelShowingTerminal && (activeTerminalInstance?.id === terminalData.terminal.id); } @@ -312,7 +312,7 @@ export class TerminalTaskSystem implements ITaskSystem { this.previousTerminalInstance = undefined; } else { this.previousPanelId = this.panelService.getActivePanel()?.getId(); - if (this.previousPanelId === TERMINAL_PANEL_ID) { + if (this.previousPanelId === TERMINAL_VIEW_ID) { this.previousTerminalInstance = this.terminalService.getActiveInstance() ?? undefined; } this.terminalService.setActiveInstance(terminalData.terminal); diff --git a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css index b23ca300e2..f6ad21f8dd 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css +++ b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .panel.integrated-terminal .xterm-viewport { +.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { /* Use the hack presented in http://stackoverflow.com/a/38748186/1156119 to get opacity transitions working on the scrollbar */ -webkit-background-clip: text; background-clip: text; @@ -11,31 +11,31 @@ transition: background-color 800ms linear; } -.monaco-workbench .panel.integrated-terminal .xterm-viewport::-webkit-scrollbar { +.monaco-workbench .pane-body.integrated-terminal .xterm-viewport::-webkit-scrollbar { width: 10px; } -.monaco-workbench .panel.integrated-terminal .xterm-viewport::-webkit-scrollbar-track { +.monaco-workbench .pane-body.integrated-terminal .xterm-viewport::-webkit-scrollbar-track { opacity: 0; } -.monaco-workbench .panel.integrated-terminal .xterm-viewport::-webkit-scrollbar-thumb { +.monaco-workbench .pane-body.integrated-terminal .xterm-viewport::-webkit-scrollbar-thumb { min-height: 20px; background-color: inherit; } -.monaco-workbench .panel.integrated-terminal .find-focused .xterm .xterm-viewport, -.monaco-workbench .panel.integrated-terminal .xterm.focus .xterm-viewport, -.monaco-workbench .panel.integrated-terminal .xterm:focus .xterm-viewport, -.monaco-workbench .panel.integrated-terminal .xterm:hover .xterm-viewport { +.monaco-workbench .pane-body.integrated-terminal .find-focused .xterm .xterm-viewport, +.monaco-workbench .pane-body.integrated-terminal .xterm.focus .xterm-viewport, +.monaco-workbench .pane-body.integrated-terminal .xterm:focus .xterm-viewport, +.monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { transition: opacity 100ms linear; cursor: default; } -.monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { +.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { transition: opacity 0ms linear; } -.monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive { +.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive { background-color: inherit; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index e419ea6d89..86c602b25f 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .panel.integrated-terminal { +.monaco-workbench .pane-body.integrated-terminal { align-content: flex-start; align-items: baseline; display: flex; @@ -14,51 +14,51 @@ position: relative; } -.monaco-workbench .panel.integrated-terminal .terminal-outer-container { +.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container { height: 100%; width: 100%; box-sizing: border-box; overflow: hidden; } -.monaco-workbench .panel.integrated-terminal .terminal-tab { +.monaco-workbench .pane-body.integrated-terminal .terminal-tab { height: 100%; } -.monaco-workbench .panel.integrated-terminal .terminal-wrapper { +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper { display: none; margin: 0 10px; } -.monaco-workbench .panel.integrated-terminal .terminal-wrapper.active { +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.active { display: block; position: absolute; bottom: 2px; /* Matches padding-bottom on .terminal-outer-container */ top: 0; } -.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper { +.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper { margin-left: 20px; } -.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper { +.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper { margin-right: 20px; } -.monaco-workbench .panel.integrated-terminal .xterm a:not(.xterm-invalid-link) { +.monaco-workbench .pane-body.integrated-terminal .xterm a:not(.xterm-invalid-link) { /* To support message box sizing */ position: relative; } -.monaco-workbench .panel.integrated-terminal .terminal-wrapper > div { +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper > div { height: 100%; } -.monaco-workbench .panel.integrated-terminal .xterm-viewport { +.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { margin-right: -10px; } -.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport { +.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport { margin-right: -20px; } -.monaco-workbench .panel.integrated-terminal canvas { +.monaco-workbench .pane-body.integrated-terminal canvas { /* Align the viewport and canvases to the bottom of the panel */ position: absolute; right: -20px; @@ -68,58 +68,58 @@ top: auto; } -.monaco-workbench .panel.integrated-terminal { +.monaco-workbench .pane-body.integrated-terminal { font-variant-ligatures: none; } -.monaco-workbench .panel.integrated-terminal .split-view-view { +.monaco-workbench .pane-body.integrated-terminal .split-view-view { box-sizing: border-box; } /* border-color is set by theme key terminal.border */ -.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:first-child) { +.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:first-child) { border-left-width: 1px; border-left-style: solid; } -.monaco-workbench .panel.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:first-child) { +.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:first-child) { border-top-width: 1px; border-top-style: solid; } -.monaco-workbench .panel.integrated-terminal.enable-ligatures { +.monaco-workbench .pane-body.integrated-terminal.enable-ligatures { font-variant-ligatures: normal; } -.monaco-workbench .panel.integrated-terminal.disable-bold .xterm-bold { +.monaco-workbench .pane-body.integrated-terminal.disable-bold .xterm-bold { font-weight: normal !important; } /* Use the default cursor when alt is active to help with clicking to move cursor */ -.monaco-workbench .panel.integrated-terminal .terminal-outer-container.alt-active .xterm { +.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container.alt-active .xterm { cursor: default; } -.monaco-workbench .panel.integrated-terminal .xterm { +.monaco-workbench .pane-body.integrated-terminal .xterm { position: absolute; bottom: 0; left: 0; user-select: none; -webkit-user-select: none; } -.monaco-workbench .panel.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:last-child) .xterm { +.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:last-child) .xterm { /* When vertical and NOT the bottom terminal, align to the top instead to prevent the output jumping around erratically */ top: 0; bottom: auto; } -.monaco-workbench .panel.integrated-terminal .xterm:focus { +.monaco-workbench .pane-body.integrated-terminal .xterm:focus { /* Hide outline when focus jumps from xterm to the text area */ outline: none; } -.hc-black .monaco-workbench .panel.integrated-terminal .xterm.focus::before, -.hc-black .monaco-workbench .panel.integrated-terminal .xterm:focus::before { +.hc-black .monaco-workbench .pane-body.integrated-terminal .xterm.focus::before, +.hc-black .monaco-workbench .pane-body.integrated-terminal .xterm:focus::before { display: block; content: ""; border: 1px solid; @@ -131,23 +131,23 @@ z-index: 10; } -.hc-black .monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:only-child) .xterm.focus::before, -.hc-black .monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:only-child) .xterm:focus::before { +.hc-black .monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:only-child) .xterm.focus::before, +.hc-black .monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:only-child) .xterm:focus::before { right: 0; } -.monaco-workbench .panel.integrated-terminal .xterm .xterm-helpers { +.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-helpers { position: absolute; top: 0; } -.monaco-workbench .panel.integrated-terminal .xterm .xterm-helper-textarea:focus { +.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-helper-textarea:focus { /* Override the general vscode style applies `opacity:1!important` to textareas */ opacity: 0 !important; } -.vs-dark .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events), -.hc-black .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events) { +.vs-dark .monaco-workbench.mac .pane-body.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events), +.hc-black .monaco-workbench.mac .pane-body.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events) { cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index e6a8cf77dc..39f87496f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -20,10 +20,11 @@ import * as panel from 'vs/workbench/browser/panel'; import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalAction, RenameWithArgTerminalAction, SendSequenceTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; -import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; +import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; -import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_VIEW_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { setupTerminalCommands } from 'vs/workbench/contrib/terminal/browser/terminalCommands'; import { setupTerminalMenu } from 'vs/workbench/contrib/terminal/common/terminalMenu'; @@ -36,6 +37,8 @@ import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; registerSingleton(ITerminalService, TerminalService, true); @@ -362,15 +365,20 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenTermAction const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor); -(Registry.as(panel.Extensions.Panels)).registerPanel(panel.PanelDescriptor.create( - TerminalPanel, - TERMINAL_PANEL_ID, - nls.localize('terminal', "Terminal"), - 'terminal', - 40, - TERMINAL_COMMAND_ID.TOGGLE -)); -Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERMINAL_PANEL_ID); +const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ + id: TERMINAL_VIEW_ID, + name: nls.localize('terminal', "Terminal"), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS } +}, ViewContainerLocation.Panel); +Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERMINAL_VIEW_ID); + +Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ + id: TERMINAL_VIEW_ID, + name: nls.localize('terminal', "Terminal"), + canToggleVisibility: false, + ctorDescriptor: new SyncDescriptor(TerminalViewPane) +}], VIEW_CONTAINER); // On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl const category = TERMINAL_ACTION_CATEGORY; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 48120683b1..1f3251a260 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { TERMINAL_PANEL_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -78,7 +78,7 @@ export class ToggleTerminalAction extends TogglePanelAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITerminalService private readonly terminalService: ITerminalService ) { - super(id, label, TERMINAL_PANEL_ID, panelService, layoutService); + super(id, label, TERMINAL_VIEW_ID, panelService, layoutService); } public run(event?: any): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index de25a8c7a7..13a465d037 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,11 +25,10 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; -import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, TERMINAL_COMMAND_ID, LEGACY_CONSOLE_MODE_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, TERMINAL_COMMAND_ID, LEGACY_CONSOLE_MODE_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstanceService, ITerminalInstance, TerminalShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; @@ -41,6 +40,7 @@ import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addon import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IViewsService } from 'vs/workbench/common/views'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -282,7 +282,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @INotificationService private readonly _notificationService: INotificationService, - @IPanelService private readonly _panelService: IPanelService, + @IViewsService private readonly _viewsService: IViewsService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IClipboardService private readonly _clipboardService: IClipboardService, @IThemeService private readonly _themeService: IThemeService, @@ -963,8 +963,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } private _refreshSelectionContextKey() { - const activePanel = this._panelService.getActivePanel(); - const isActive = !!activePanel && activePanel.getId() === TERMINAL_PANEL_ID; + const isActive = !!this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID); this._terminalHasTextContextKey.set(isActive && this.hasSelection()); } @@ -1507,8 +1506,8 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const border = theme.getColor(activeContrastBorder); if (border) { collector.addRule(` - .hc-black .monaco-workbench .panel.integrated-terminal .xterm.focus::before, - .hc-black .monaco-workbench .panel.integrated-terminal .xterm:focus::before { border-color: ${border}; }` + .hc-black .monaco-workbench .pane-body.integrated-terminal .xterm.focus::before, + .hc-black .monaco-workbench .pane-body.integrated-terminal .xterm:focus::before { border-color: ${border}; }` ); } @@ -1516,20 +1515,20 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground); if (scrollbarSliderBackgroundColor) { collector.addRule(` - .monaco-workbench .panel.integrated-terminal .find-focused .xterm .xterm-viewport, - .monaco-workbench .panel.integrated-terminal .xterm.focus .xterm-viewport, - .monaco-workbench .panel.integrated-terminal .xterm:focus .xterm-viewport, - .monaco-workbench .panel.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }` + .monaco-workbench .pane-body.integrated-terminal .find-focused .xterm .xterm-viewport, + .monaco-workbench .pane-body.integrated-terminal .xterm.focus .xterm-viewport, + .monaco-workbench .pane-body.integrated-terminal .xterm:focus .xterm-viewport, + .monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }` ); } const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground); if (scrollbarSliderHoverBackgroundColor) { - collector.addRule(`.monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; }`); + collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; }`); } const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground); if (scrollbarSliderActiveBackgroundColor) { - collector.addRule(`.monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { background-color: ${scrollbarSliderActiveBackgroundColor}; }`); + collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { background-color: ${scrollbarSliderActiveBackgroundColor}; }`); } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 3cd0d4dafe..97e0f620fb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; +import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; @@ -29,6 +29,7 @@ import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform import { basename } from 'vs/base/common/path'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { find } from 'vs/base/common/arrays'; +import { IViewsService } from 'vs/workbench/common/views'; interface IExtHostReadyEntry { promise: Promise; @@ -100,6 +101,7 @@ export class TerminalService implements ITerminalService { @IRemoteAgentService private _remoteAgentService: IRemoteAgentService, @IQuickInputService private _quickInputService: IQuickInputService, @IConfigurationService private _configurationService: IConfigurationService, + @IViewsService private _viewsService: IViewsService, @optional(ITerminalNativeService) terminalNativeService: ITerminalNativeService ) { // @optional could give undefined and properly typing it breaks service registration @@ -421,9 +423,9 @@ export class TerminalService implements ITerminalService { public showPanel(focus?: boolean): Promise { return new Promise(async (complete) => { - const panel = this._panelService.getActivePanel(); - if (!panel || panel.getId() !== TERMINAL_PANEL_ID) { - await this._panelService.openPanel(TERMINAL_PANEL_ID, focus); + const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane; + if (!pane) { + await this._panelService.openPanel(TERMINAL_VIEW_ID, focus); if (focus) { // Do the focus call asynchronously as going through the // command palette will force editor focus @@ -655,34 +657,34 @@ export class TerminalService implements ITerminalService { public focusFindWidget(): Promise { return this.showPanel(false).then(() => { - const panel = this._panelService.getActivePanel() as TerminalPanel; - panel.focusFindWidget(); + const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane; + pane.focusFindWidget(); this._findWidgetVisible.set(true); }); } public hideFindWidget(): void { - const panel = this._panelService.getActivePanel() as TerminalPanel; - if (panel && panel.getId() === TERMINAL_PANEL_ID) { - panel.hideFindWidget(); + const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane; + if (pane) { + pane.hideFindWidget(); this._findWidgetVisible.reset(); - panel.focus(); + pane.focus(); } } public findNext(): void { - const panel = this._panelService.getActivePanel() as TerminalPanel; - if (panel && panel.getId() === TERMINAL_PANEL_ID) { - panel.showFindWidget(); - panel.getFindWidget().find(false); + const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane; + if (pane) { + pane.showFindWidget(); + pane.getFindWidget().find(false); } } public findPrevious(): void { - const panel = this._panelService.getActivePanel() as TerminalPanel; - if (panel && panel.getId() === TERMINAL_PANEL_ID) { - panel.showFindWidget(); - panel.getFindWidget().find(true); + const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane; + if (pane) { + pane.showFindWidget(); + pane.getFindWidget().find(true); } } @@ -694,7 +696,7 @@ export class TerminalService implements ITerminalService { public hidePanel(): void { const panel = this._panelService.getActivePanel(); - if (panel && panel.getId() === TERMINAL_PANEL_ID) { + if (panel && panel.getId() === TERMINAL_VIEW_ID) { this._layoutService.setPanelHidden(true); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts similarity index 85% rename from src/vs/workbench/contrib/terminal/browser/terminalPanel.ts rename to src/vs/workbench/contrib/terminal/browser/terminalView.ts index 8b6a43e5a4..b3ae2b465c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -12,12 +12,10 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget'; import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionViewItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; -import { Panel } from 'vs/workbench/browser/panel'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { URI } from 'vs/base/common/uri'; import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; @@ -25,13 +23,15 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { assertIsDefined } from 'vs/base/common/types'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const FIND_FOCUS_CLASS = 'find-focused'; -export class TerminalPanel extends Panel { - +export class TerminalViewPane extends ViewPane { private _actions: IAction[] | undefined; private _copyContextMenuAction: IAction | undefined; private _contextMenuActions: IAction[] | undefined; @@ -42,21 +42,24 @@ export class TerminalPanel extends Panel { private _findWidget: TerminalFindWidget | undefined; constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, + options: IViewPaneOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IConfigurationService configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITerminalService private readonly _terminalService: ITerminalService, - @IThemeService protected readonly _themeService: IThemeService, + @IThemeService protected readonly themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, @INotificationService private readonly _notificationService: INotificationService, @IStorageService storageService: IStorageService ) { - super(TERMINAL_PANEL_ID, telemetryService, _themeService, storageService); + super(options, keybindingService, _contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService); } - public create(parent: HTMLElement): void { - super.create(parent); - this._parentDomElement = parent; + protected renderBody(container: HTMLElement): void { + this._parentDomElement = container; dom.addClass(this._parentDomElement, 'integrated-terminal'); this._fontStyleElement = document.createElement('style'); @@ -72,11 +75,10 @@ export class TerminalPanel extends Panel { this._attachEventListeners(this._parentDomElement, this._terminalContainer); - const container = assertIsDefined(this.getContainer()); this._terminalService.setContainers(container, this._terminalContainer); this._register(this.themeService.onThemeChange(theme => this._updateTheme(theme))); - this._register(this._configurationService.onDidChangeConfiguration(e => { + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fontFamily')) { this._updateFont(); } @@ -86,7 +88,7 @@ export class TerminalPanel extends Panel { if (!configHelper.configFontIsMonospace()) { const choices: IPromptChoice[] = [{ label: nls.localize('terminal.useMonospace', "Use 'monospace'"), - run: () => this._configurationService.updateValue('terminal.integrated.fontFamily', 'monospace'), + run: () => this.configurationService.updateValue('terminal.integrated.fontFamily', 'monospace'), }]; this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts. Be sure to restart VS Code if this is a newly installed font."), choices); } @@ -95,7 +97,7 @@ export class TerminalPanel extends Panel { this._updateFont(); this._updateTheme(); - this._register(this.onDidChangeVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (visible) { const hadTerminals = this._terminalService.terminalInstances.length > 0; if (!hadTerminals) { @@ -110,14 +112,11 @@ export class TerminalPanel extends Panel { })); // Force another layout (first is setContainers) since config has changed - this.layout(new dom.Dimension(this._terminalContainer.offsetWidth, this._terminalContainer.offsetHeight)); + this.layoutBody(this._terminalContainer.offsetWidth, this._terminalContainer.offsetHeight); } - public layout(dimension?: dom.Dimension): void { - if (!dimension) { - return; - } - this._terminalService.terminalTabs.forEach(t => t.layout(dimension.width, dimension.height)); + protected layoutBody(height: number, width: number): void { + this._terminalService.terminalTabs.forEach(t => t.layout(width, height)); } public getActions(): IAction[] { @@ -321,30 +320,30 @@ export class TerminalPanel extends Panel { } // TODO: Can we support ligatures? // dom.toggleClass(this._parentDomElement, 'enable-ligatures', this._terminalService.configHelper.config.fontLigatures); - this.layout(new dom.Dimension(this._parentDomElement.offsetWidth, this._parentDomElement.offsetHeight)); + this.layoutBody(this._parentDomElement.offsetWidth, this._parentDomElement.offsetHeight); } } registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR); - collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-outer-container { background-color: ${backgroundColor ? backgroundColor.toString() : ''}; }`); + collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container { background-color: ${backgroundColor ? backgroundColor.toString() : ''}; }`); const borderColor = theme.getColor(TERMINAL_BORDER_COLOR); if (borderColor) { - collector.addRule(`.monaco-workbench .panel.integrated-terminal .split-view-view:not(:first-child) { border-color: ${borderColor.toString()}; }`); + collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .split-view-view:not(:first-child) { border-color: ${borderColor.toString()}; }`); } // Borrow the editor's hover background for now const hoverBackground = theme.getColor(editorHoverBackground); if (hoverBackground) { - collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { background-color: ${hoverBackground}; }`); + collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .terminal-message-widget { background-color: ${hoverBackground}; }`); } const hoverBorder = theme.getColor(editorHoverBorder); if (hoverBorder) { - collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { border: 1px solid ${hoverBorder}; }`); + collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .terminal-message-widget { border: 1px solid ${hoverBorder}; }`); } const hoverForeground = theme.getColor(editorHoverForeground); if (hoverForeground) { - collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { color: ${hoverForeground}; }`); + collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .terminal-message-widget { color: ${hoverForeground}; }`); } }); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index c8ae79dd54..df5d245877 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { OperatingSystem } from 'vs/base/common/platform'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; -export const TERMINAL_PANEL_ID = 'workbench.panel.terminal'; +export const TERMINAL_VIEW_ID = 'workbench.panel.terminal'; /** A context key that is set when there is at least one opened integrated terminal. */ export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey('terminalIsOpen', false); diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts similarity index 100% rename from src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts rename to src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts similarity index 100% rename from src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts rename to src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts similarity index 100% rename from src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts rename to src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts similarity index 100% rename from src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts rename to src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 81fb1db418..e14c3010ec 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -289,10 +289,7 @@ export class TimelinePane extends ViewPane { container.appendChild(this._treeElement); const renderer = this.instantiationService.createInstance(TimelineTreeRenderer); - this._tree = this.instantiationService.createInstance< - typeof WorkbenchObjectTree, - WorkbenchObjectTree - >(WorkbenchObjectTree, 'TimelinePane', this._treeElement, new TimelineListVirtualDelegate(), [renderer], { + this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', this._treeElement, new TimelineListVirtualDelegate(), [renderer], { identityProvider: new TimelineIdentityProvider(), keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider() }); diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts index 6690032e96..27cbd96a2e 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -46,8 +46,7 @@ export interface TimelineProvider extends TimelineProviderDescriptor, IDisposabl export interface TimelineProviderDescriptor { id: string; label: string; - - // selector: DocumentSelector; + scheme: string | string[]; } export interface TimelineProvidersChangeEvent { diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index b4eaa997b0..c75aee9d2d 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -87,6 +87,13 @@ export class TimelineService implements ITimelineService { const requests: Promise<[string, TimelineItem[]]>[] = []; for (const provider of this._providers.values()) { + if (typeof provider.scheme === 'string') { + if (provider.scheme !== '*' && provider.scheme !== uri.scheme) { + continue; + } + } else if (!provider.scheme.includes(uri.scheme)) { + continue; + } if (!(predicate?.(provider) ?? true)) { continue; } @@ -117,6 +124,14 @@ export class TimelineService implements ITimelineService { return undefined; } + if (typeof provider.scheme === 'string') { + if (provider.scheme !== '*' && provider.scheme !== uri.scheme) { + return undefined; + } + } else if (!provider.scheme.includes(uri.scheme)) { + return undefined; + } + return { items: provider.provideTimeline(uri, tokenSource.token) .then(items => { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts index b8f38dda9e..25d97bab79 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Event } from 'vs/base/common/event'; import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; @@ -14,15 +14,15 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { constructor( + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService userDataSyncService: IUserDataSyncService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService, @IInstantiationService instantiationService: IInstantiationService, @IHostService hostService: IHostService, - @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, ) { - super(configurationService, userDataSyncService, logService, authTokenService, userDataSyncUtilService); + super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService); // Sync immediately if there is a local change. this._register(Event.debounce(Event.any( diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 2c9482fb8c..bce029f9e5 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -3,10 +3,46 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; + +class UserDataSyncSettingsMigrationContribution implements IWorkbenchContribution { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, + ) { + if (!configurationService.getValue('sync.enableSettings')) { + userDataSyncEnablementService.setResourceEnablement('settings', false); + } + if (!configurationService.getValue('sync.enableKeybindings')) { + userDataSyncEnablementService.setResourceEnablement('keybindings', false); + } + if (!configurationService.getValue('sync.enableUIState')) { + userDataSyncEnablementService.setResourceEnablement('globalState', false); + } + if (!configurationService.getValue('sync.enableExtensions')) { + userDataSyncEnablementService.setResourceEnablement('extensions', false); + } + if (configurationService.getValue('sync.enable')) { + userDataSyncEnablementService.setEnablement(true); + } + this.removeFromConfiguration(); + } + + private async removeFromConfiguration(): Promise { + await this.configurationService.updateValue('sync.enable', undefined, ConfigurationTarget.USER); + await this.configurationService.updateValue('sync.enableSettings', undefined, ConfigurationTarget.USER); + await this.configurationService.updateValue('sync.enableKeybindings', undefined, ConfigurationTarget.USER); + await this.configurationService.updateValue('sync.enableUIState', undefined, ConfigurationTarget.USER); + await this.configurationService.updateValue('sync.enableExtensions', undefined, ConfigurationTarget.USER); + } +} const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution(UserDataSyncSettingsMigrationContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 01e9643031..c802eb3f80 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode, UserDataSyncError, getSyncSourceFromPreviewResource, IUserDataSyncEnablementService, ResourceKey } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; -import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; -import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey'; import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -51,9 +51,11 @@ const enum AuthStatus { SignedOut = 'SignedOut', Unavailable = 'Unavailable' } +const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthStatus.Initializing); +const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); -type ConfigureSyncQuickPickItem = { id: string, label: string, description?: string }; +type ConfigureSyncQuickPickItem = { id: ResourceKey, label: string, description?: string }; function getSyncAreaLabel(source: SyncSource): string { switch (source) { @@ -70,23 +72,24 @@ type FirstTimeSyncClassification = { export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { - private static readonly ENABLEMENT_SETTING = 'sync.enable'; - private readonly userDataSyncStore: IUserDataSyncStore | undefined; + private readonly syncEnablementContext: IContextKey; private readonly syncStatusContext: IContextKey; private readonly authenticationState: IContextKey; + private readonly conflictsSources: IContextKey; + private readonly conflictsDisposables = new Map(); private readonly badgeDisposable = this._register(new MutableDisposable()); - private readonly conflictsWarningDisposable = this._register(new MutableDisposable()); private readonly signInNotificationDisposable = this._register(new MutableDisposable()); private _activeAccount: AuthenticationSession | undefined; constructor( + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IContextKeyService contextKeyService: IContextKeyService, @IActivityService private readonly activityService: IActivityService, @INotificationService private readonly notificationService: INotificationService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IDialogService private readonly dialogService: IDialogService, @@ -101,13 +104,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); + this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); this.authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService); + this.conflictsSources = CONTEXT_CONFLICTS_SOURCES.bindTo(contextKeyService); if (this.userDataSyncStore) { registerConfiguration(); this.onDidChangeSyncStatus(this.userDataSyncService.status); + this.onDidChangeConflicts(this.userDataSyncService.conflictsSources); + this.onDidChangeEnablement(this.userDataSyncEnablementService.isEnabled()); this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING))(() => this.onDidChangeEnablement())); + this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflictsSources))); + this._register(this.userDataSyncEnablementService.onDidChangeEnablement(enabled => this.onDidChangeEnablement(enabled))); this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidChangeSessions(e => this.onDidChangeSessions(e))); @@ -221,44 +229,71 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private onDidChangeSyncStatus(status: SyncStatus) { this.syncStatusContext.set(status); - if (status === SyncStatus.Syncing) { // Show syncing progress if takes more than 1s. timeout(1000).then(() => this.updateBadge()); } else { this.updateBadge(); } + } - if (this.userDataSyncService.status === SyncStatus.HasConflicts) { - const conflictsEditorInput = this.getConflictsEditorInput(this.userDataSyncService.conflictsSource!); - if (!conflictsEditorInput && !this.conflictsWarningDisposable.value) { - const conflictsArea = getSyncAreaLabel(this.userDataSyncService.conflictsSource!); - const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea), - [ - { - label: localize('show conflicts', "Show Conflicts"), - run: () => { - this.telemetryService.publicLog2('sync/showConflicts'); - this.handleConflicts(); + private onDidChangeConflicts(conflicts: SyncSource[]) { + this.updateBadge(); + if (conflicts.length) { + this.conflictsSources.set(this.userDataSyncService.conflictsSources.join(',')); + + // Clear and dispose conflicts those were cleared + this.conflictsDisposables.forEach((disposable, conflictsSource) => { + if (this.userDataSyncService.conflictsSources.indexOf(conflictsSource) === -1) { + disposable.dispose(); + this.conflictsDisposables.delete(conflictsSource); + } + }); + + for (const conflictsSource of this.userDataSyncService.conflictsSources) { + const conflictsEditorInput = this.getConflictsEditorInput(conflictsSource); + if (!conflictsEditorInput && !this.conflictsDisposables.has(conflictsSource)) { + const conflictsArea = getSyncAreaLabel(conflictsSource); + const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea), + [ + { + label: localize('show conflicts', "Show Conflicts"), + run: () => { + this.telemetryService.publicLog2('sync/showConflicts'); + this.handleConflicts(conflictsSource); + } } + ], + { + sticky: true } - ], - { - sticky: true - } - ); - this.conflictsWarningDisposable.value = toDisposable(() => handle.close()); - handle.onDidClose(() => this.conflictsWarningDisposable.clear()); + ); + this.conflictsDisposables.set(conflictsSource, toDisposable(() => { + + // close the conflicts warning notification + handle.close(); + + // close opened conflicts editor previews + const conflictsEditorInput = this.getConflictsEditorInput(conflictsSource); + if (conflictsEditorInput) { + conflictsEditorInput.dispose(); + } + + this.conflictsDisposables.delete(conflictsSource); + })); + } } } else { + this.conflictsSources.reset(); this.getAllConflictsEditorInputs().forEach(input => input.dispose()); - this.conflictsWarningDisposable.clear(); + this.conflictsDisposables.forEach(disposable => disposable.dispose()); + this.conflictsDisposables.clear(); } } - private onDidChangeEnablement() { + private onDidChangeEnablement(enabled: boolean) { + this.syncEnablementContext.set(enabled); this.updateBadge(); - const enabled = this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING); if (enabled) { if (this.authenticationState.get() === AuthStatus.SignedOut) { const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId); @@ -302,10 +337,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo let clazz: string | undefined; let priority: number | undefined = undefined; - if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authenticationState.get() === AuthStatus.SignedOut) { + if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncEnablementService.isEnabled() && this.authenticationState.get() === AuthStatus.SignedOut) { badge = new NumberBadge(1, () => localize('sign in to sync', "Sign in to Sync")); - } else if (this.userDataSyncService.status === SyncStatus.HasConflicts) { - badge = new NumberBadge(1, () => localize('show conflicts', "Show Conflicts")); + } else if (this.userDataSyncService.conflictsSources.length) { + badge = new NumberBadge(this.userDataSyncService.conflictsSources.length, () => localize('has conflicts', "Sync: Conflicts Detected")); } else if (this.userDataSyncService.status === SyncStatus.Syncing) { badge = new ProgressBadge(() => localize('syncing', "Synchronizing User Configuration...")); clazz = 'progress-badge'; @@ -337,10 +372,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo quickPick.ignoreFocusOut = true; const items = this.getConfigureSyncQuickPickItems(); quickPick.items = items; - quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); + quickPick.selectedItems = items.filter(item => this.userDataSyncEnablementService.isResourceEnabled(item.id)); disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(async () => { if (quickPick.selectedItems.length) { - await this.updateConfiguration(items, quickPick.selectedItems); + this.updateConfiguration(items, quickPick.selectedItems); this.doTurnOn().then(c, e); quickPick.hide(); } @@ -355,32 +390,32 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo await this.signIn(); } await this.handleFirstTimeSync(); - await this.enableSync(); + this.userDataSyncEnablementService.setEnablement(true); } private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { return [{ - id: 'sync.enableSettings', + id: 'settings', label: getSyncAreaLabel(SyncSource.Settings) }, { - id: 'sync.enableKeybindings', + id: 'keybindings', label: getSyncAreaLabel(SyncSource.Keybindings) }, { - id: 'sync.enableExtensions', + id: 'extensions', label: getSyncAreaLabel(SyncSource.Extensions) }, { - id: 'sync.enableUIState', + id: 'globalState', label: getSyncAreaLabel(SyncSource.GlobalState), description: localize('ui state description', "only 'Display Language' for now") }]; } - private async updateConfiguration(items: ConfigureSyncQuickPickItem[], selectedItems: ReadonlyArray): Promise { + private updateConfiguration(items: ConfigureSyncQuickPickItem[], selectedItems: ReadonlyArray): void { for (const item of items) { - const wasEnabled = this.configurationService.getValue(item.id); + const wasEnabled = this.userDataSyncEnablementService.isResourceEnabled(item.id); const isEnabled = !!selectedItems.filter(selected => selected.id === item.id)[0]; if (wasEnabled !== isEnabled) { - await this.configurationService.updateValue(item.id!, isEnabled); + this.userDataSyncEnablementService.setResourceEnablement(item.id!, isEnabled); } } } @@ -394,9 +429,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync"); quickPick.canSelectMany = true; quickPick.ignoreFocusOut = true; + quickPick.ok = true; const items = this.getConfigureSyncQuickPickItems(); quickPick.items = items; - quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); + quickPick.selectedItems = items.filter(item => this.userDataSyncEnablementService.isResourceEnabled(item.id)); disposables.add(quickPick.onDidAccept(async () => { if (quickPick.selectedItems.length) { await this.updateConfiguration(items, quickPick.selectedItems); @@ -412,12 +448,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async handleFirstTimeSync(): Promise { - const hasRemote = await this.userDataSyncService.hasRemoteData(); - if (!hasRemote) { - return; - } - const isFirstSyncAndHasUserData = await this.userDataSyncService.isFirstTimeSyncAndHasUserData(); - if (!isFirstSyncAndHasUserData) { + const isFirstSyncWithMerge = await this.userDataSyncService.isFirstTimeSyncWithMerge(); + if (!isFirstSyncWithMerge) { return; } const result = await this.dialogService.show( @@ -447,10 +479,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private enableSync(): Promise { - return this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); - } - private async turnOff(): Promise { const result = await this.dialogService.confirm({ type: 'info', @@ -472,15 +500,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private disableSync(source?: SyncSource): Promise { - let key: string = UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING; - switch (source) { - case SyncSource.Settings: key = 'sync.enableSettings'; break; - case SyncSource.Keybindings: key = 'sync.enableKeybindings'; break; - case SyncSource.Extensions: key = 'sync.enableExtensions'; break; - case SyncSource.GlobalState: key = 'sync.enableUIState'; break; + private disableSync(source?: SyncSource): void { + if (source === undefined) { + this.userDataSyncEnablementService.setEnablement(false); + } else { + switch (source) { + case SyncSource.Settings: return this.userDataSyncEnablementService.setResourceEnablement('settings', false); + case SyncSource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement('keybindings', false); + case SyncSource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement('extensions', false); + case SyncSource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement('globalState', false); + } } - return this.configurationService.updateValue(key, false, ConfigurationTarget.USER); } private async signIn(): Promise { @@ -513,18 +543,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } - private async handleConflicts(): Promise { + private async handleConflicts(source: SyncSource): Promise { let previewResource: URI | undefined = undefined; let label: string = ''; - if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { + if (source === SyncSource.Settings) { previewResource = this.workbenchEnvironmentService.settingsSyncPreviewResource; label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)"); - } else if (this.userDataSyncService.conflictsSource === SyncSource.Keybindings) { + } else if (source === SyncSource.Keybindings) { previewResource = this.workbenchEnvironmentService.keybindingsSyncPreviewResource; label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); } if (previewResource) { - const remoteContentResource = toRemoteContentResource(this.userDataSyncService.conflictsSource!); + const remoteContentResource = toRemoteContentResource(source); await this.editorService.openEditor({ leftResource: remoteContentResource, rightResource: previewResource, @@ -545,7 +575,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerActions(): void { const turnOnSyncCommandId = 'workbench.userData.actions.syncStart'; - const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthStatus.Initializing)); + const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthStatus.Initializing)); CommandsRegistry.registerCommand(turnOnSyncCommandId, async () => { try { await this.turnOn(); @@ -572,7 +602,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); const signInCommandId = 'workbench.userData.actions.signin'; - const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)); + const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)); CommandsRegistry.registerCommand(signInCommandId, () => this.signIn()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', @@ -598,33 +628,52 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo id: stopSyncCommandId, title: localize('global activity stop sync', "Turn off Sync") }, - when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) + when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: stopSyncCommandId, title: localize('stop sync', "Sync: Turn off Sync") }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)), + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), }); - const resolveConflictsCommandId = 'workbench.userData.actions.resolveConflicts'; - const resolveConflictsWhenContext = CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts); - CommandsRegistry.registerCommand(resolveConflictsCommandId, () => this.handleConflicts()); + const resolveSettingsConflictsCommandId = 'workbench.userData.actions.resolveSettingsConflicts'; + const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); + CommandsRegistry.registerCommand(resolveSettingsConflictsCommandId, () => this.handleConflicts(SyncSource.Settings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: resolveConflictsCommandId, - title: localize('resolveConflicts_global', "Show Sync Conflicts (1)"), + id: resolveSettingsConflictsCommandId, + title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), }, - when: resolveConflictsWhenContext, + when: resolveSettingsConflictsWhenContext, }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { - id: resolveConflictsCommandId, - title: localize('showConflicts', "Sync: Show Sync Conflicts"), + id: resolveSettingsConflictsCommandId, + title: localize('showConflicts', "Sync: Show Settings Conflicts"), }, - when: resolveConflictsWhenContext, + when: resolveSettingsConflictsWhenContext, + }); + + const resolveKeybindingsConflictsCommandId = 'workbench.userData.actions.resolveKeybindingsConflicts'; + const resolveKeybindingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); + CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommandId, () => this.handleConflicts(SyncSource.Keybindings)); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: { + id: resolveKeybindingsConflictsCommandId, + title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), + }, + when: resolveKeybindingsConflictsWhenContext, + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: resolveKeybindingsConflictsCommandId, + title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts"), + }, + when: resolveKeybindingsConflictsWhenContext, }); const signOutMenuItem: IMenuItem = { @@ -645,7 +694,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo id: configureSyncCommandId, title: localize('configure sync', "Sync: Configure") }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)), + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), }); const showSyncLogCommandId = 'workbench.userData.actions.showSyncLog'; @@ -745,7 +794,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio return false; // we need a model } - if (this.isSyncPreviewResource(model.uri)) { + if (getSyncSourceFromPreviewResource(model.uri, this.environmentService) !== undefined) { return true; } @@ -756,50 +805,37 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio return false; } - private isSyncPreviewResource(uri: URI): boolean { - if (isEqual(uri, this.environmentService.settingsSyncPreviewResource)) { - return true; - } - - if (isEqual(uri, this.environmentService.keybindingsSyncPreviewResource)) { - return true; - } - - return false; - } private createAcceptChangesWidgetRenderer(): void { if (!this.acceptChangesButton) { + const isRemote = getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined; const acceptRemoteLabel = localize('accept remote', "Accept Remote"); const acceptLocalLabel = localize('accept local', "Accept Local"); - this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? acceptRemoteLabel : acceptLocalLabel, null); + this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptLocalLabel, null); this._register(this.acceptChangesButton.onClick(async () => { const model = this.editor.getModel(); if (model) { - const conflictsSource = this.userDataSyncService.conflictsSource; - const syncSource = getSyncSourceFromRemoteContentResource(model.uri); - this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource!, action: syncSource !== undefined ? 'acceptRemote' : 'acceptLocal' }); - const syncAreaLabel = getSyncAreaLabel(conflictsSource!); + const conflictsSource = (getSyncSourceFromPreviewResource(model.uri, this.environmentService) || getSyncSourceFromRemoteContentResource(model.uri))!; + this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); + const syncAreaLabel = getSyncAreaLabel(conflictsSource); const result = await this.dialogService.confirm({ type: 'info', - title: syncSource !== undefined + title: isRemote ? localize('Sync accept remote', "Sync: {0}", acceptRemoteLabel) : localize('Sync accept local', "Sync: {0}", acceptLocalLabel), - message: syncSource !== undefined + message: isRemote ? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel, syncAreaLabel) : localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel, syncAreaLabel), - primaryButton: syncSource !== undefined ? acceptRemoteLabel : acceptLocalLabel + primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel }); if (result.confirmed) { try { - await this.userDataSyncService.accept(conflictsSource!, model.getValue()); + await this.userDataSyncService.accept(conflictsSource, model.getValue()); } catch (e) { - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.NewLocal) { - this.userDataSyncService.restart().then(() => { - if (conflictsSource === this.userDataSyncService.conflictsSource) { - this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); - } - }); + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) { + if (this.userDataSyncService.conflictsSources.indexOf(conflictsSource) !== -1) { + this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); + } } else { this.notificationService.error(e); } diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 9321dde935..1d66290754 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -9,18 +9,14 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; -import { GlobalExtensionEnablementServiceChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -import { IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; class UserDataSyncServicesContribution implements IWorkbenchContribution { constructor( @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ISharedProcessService sharedProcessService: ISharedProcessService, - @IGlobalExtensionEnablementService globalExtensionEnablementService: IGlobalExtensionEnablementService, ) { sharedProcessService.registerChannel('userDataSyncUtil', new UserDataSycnUtilServiceChannel(userDataSyncUtilService)); - sharedProcessService.registerChannel('globalExtensionEnablement', new GlobalExtensionEnablementServiceChannel(globalExtensionEnablementService)); } } diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 47778afea0..c2f8239c08 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -19,13 +19,13 @@ import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/brow import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { timeout } from 'vs/base/common/async'; import { toResource } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 106f7d4233..24e44f4ddc 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -108,7 +108,7 @@ interface IBrowserWorkbenchEnvironmentConstructionOptions extends IWorkbenchCons interface IExtensionHostDebugEnvironment { params: IExtensionHostDebugParams; isExtensionDevelopment: boolean; - extensionDevelopmentLocationURI: URI[]; + extensionDevelopmentLocationURI?: URI[]; extensionTestsLocationURI?: URI; } @@ -177,7 +177,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return this._extensionHostDebugEnvironment.isExtensionDevelopment; } - get extensionDevelopmentLocationURI(): URI[] { + get extensionDevelopmentLocationURI(): URI[] | undefined { if (!this._extensionHostDebugEnvironment) { this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); } @@ -289,7 +289,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment break: false }, isExtensionDevelopment: false, - extensionDevelopmentLocationURI: [] + extensionDevelopmentLocationURI: undefined }; // Fill in selected extra environmental properties diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index f18ff0a72c..bb362b5b97 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -140,8 +140,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten } public _onExtensionHostExit(code: number): void { - console.log(`vscode:exit`, code); - // ipc.send('vscode:exit', code); + // We log the exit code to the console. Do NOT remove this + // code as the automated integration tests in browser rely + // on this message to exit properly. + console.log(`vscode:exit ${code}`); } } diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 2e0ff575df..7cce12ad21 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -395,7 +395,7 @@ registerAction2(class MeasureExtHostLatencyAction extends Action2 { id: 'editor.action.measureExtHostLatency', title: { value: nls.localize('measureExtHostLatency', "Measure Extension Host Latency"), - original: 'Developer: Measure Extension Host Latency' + original: 'Measure Extension Host Latency' }, category: nls.localize('developer', "Developer"), f1: true diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index f08e8d8faf..4152909779 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -180,8 +180,8 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { private _createProxy(rpcId: number): T { let handler = { - get: (target: any, name: string) => { - if (!target[name] && name.charCodeAt(0) === CharCode.DollarSign) { + get: (target: any, name: PropertyKey) => { + if (typeof name === 'string' && !target[name] && name.charCodeAt(0) === CharCode.DollarSign) { target[name] = (...myArgs: any[]) => { return this._remoteCall(rpcId, name, myArgs); }; diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 83d2361768..5d34808fa9 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -340,7 +340,7 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType options = options || {}; if (options.socketPath) { - return original.apply(null, arguments as unknown as any[]); + return original.apply(null, arguments as any); } const originalAgent = options.agent; @@ -376,7 +376,7 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType return original(options, callback); } - return original.apply(null, arguments as unknown as any[]); + return original.apply(null, arguments as any); } return patched; } @@ -389,7 +389,7 @@ function tlsPatches(originals: typeof tls) { function patch(original: typeof tls.createSecureContext): typeof tls.createSecureContext { return function (details: tls.SecureContextOptions): ReturnType { - const context = original.apply(null, arguments as unknown as any[]); + const context = original.apply(null, arguments as any); const certs = (details as any)._vscodeAdditionalCaCerts; if (certs) { for (const cert of certs) { diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index 83838caf74..51875b1ecd 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -43,7 +43,7 @@ function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { return class { constructor() { return new Proxy({}, { - get(target: any, prop: string | number) { + get(target: any, prop: PropertyKey) { if (target[prop]) { return target[prop]; } diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 9c531193ca..2ece2af83d 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -9,6 +9,7 @@ import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { Dimension } from 'vs/base/browser/dom'; +import { Direction } from 'vs/base/browser/ui/grid/grid'; export const IWorkbenchLayoutService = createDecorator('layoutService'); @@ -224,4 +225,9 @@ export interface IWorkbenchLayoutService extends ILayoutService { * Updates the maximized state of the window. */ updateWindowMaximizedState(maximized: boolean): void; + + /** + * Returns the next visible view part in a given direction + */ + getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined; } diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index cde09d81b7..a02b4f9d9b 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -213,7 +213,7 @@ export class FileWalker { onMessage({ message: rgCmd }); this.cmdResultCount = 0; - this.collectStdout(cmd, 'utf8', onMessage, (err: Error, stdout?: string, last?: boolean) => { + this.collectStdout(cmd, 'utf8', onMessage, (err: Error | null, stdout?: string, last?: boolean) => { if (err) { done(err); return; @@ -300,7 +300,7 @@ export class FileWalker { */ readStdout(cmd: childProcess.ChildProcess, encoding: string, cb: (err: Error | null, stdout?: string) => void): void { let all = ''; - this.collectStdout(cmd, encoding, () => { }, (err: Error, stdout?: string, last?: boolean) => { + this.collectStdout(cmd, encoding, () => { }, (err: Error | null, stdout?: string, last?: boolean) => { if (err) { cb(err); return; @@ -547,12 +547,9 @@ export class FileWalker { return clb(null, undefined); }); }); - }, (error: Error[]): void => { - if (error) { - error = arrays.coalesce(error); // find any error by removing null values first - } - - return done(error && error.length > 0 ? error[0] : undefined); + }, (error: Array | null): void => { + const filteredErrors = error ? arrays.coalesce(error) : error; // find any error by removing null values first + return done(filteredErrors && filteredErrors.length > 0 ? filteredErrors[0] : undefined); }); } @@ -622,8 +619,8 @@ export class Engine implements ISearchEngine { this.walker = new FileWalker(config); } - search(onResult: (result: IRawFileMatch) => void, onProgress: (progress: IProgressMessage) => void, done: (error: Error, complete: ISearchEngineSuccess) => void): void { - this.walker.walk(this.folderQueries, this.extraFiles, onResult, onProgress, (err: Error, isLimitHit: boolean) => { + search(onResult: (result: IRawFileMatch) => void, onProgress: (progress: IProgressMessage) => void, done: (error: Error | null, complete: ISearchEngineSuccess) => void): void { + this.walker.walk(this.folderQueries, this.extraFiles, onResult, onProgress, (err: Error | null, isLimitHit: boolean) => { done(err, { limitHit: isLimitHit, stats: this.walker.getStats() diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index d3e0cdd53a..26fa92edaf 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -16,7 +16,7 @@ import { IDebugParams } from 'vs/platform/environment/common/environment'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { FileMatch, IFileMatch, IFileQuery, IProgressMessage, IRawSearchService, ISearchComplete, ISearchConfiguration, ISearchProgressItem, ISearchResultProvider, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITextQuery, ISearchService } from 'vs/workbench/services/search/common/search'; +import { FileMatch, IFileMatch, IFileQuery, IProgressMessage, IRawSearchService, ISearchComplete, ISearchConfiguration, ISearchProgressItem, ISearchResultProvider, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITextQuery, ISearchService, isFileMatch } from 'vs/workbench/services/search/common/search'; import { SearchChannelClient } from './searchIpc'; import { SearchService } from 'vs/workbench/services/search/common/searchService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -119,8 +119,8 @@ export class DiskSearch implements ISearchResultProvider { let event: Event; event = this.raw.fileSearch(query); - const onProgress = (p: IProgressMessage) => { - if (p.message) { + const onProgress = (p: ISearchProgressItem) => { + if (!isFileMatch(p)) { // Should only be for logs this.logService.debug('SearchService#search', p.message); } diff --git a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts index 99b87d8de3..641462e16c 100644 --- a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts @@ -9,7 +9,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async import { Emitter, Event } from 'vs/base/common/event'; import * as path from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; -import { IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileMatch, ISearchEngine, ISearchEngineStats, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, QueryType } from 'vs/workbench/services/search/common/search'; +import { IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileMatch, ISearchEngine, ISearchEngineStats, ISearchEngineSuccess, ISearchProgressItem, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, isFileMatch, QueryType } from 'vs/workbench/services/search/common/search'; import { IProgressCallback, SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { DiskSearch } from 'vs/workbench/services/search/node/searchService'; @@ -36,7 +36,7 @@ class TestSearchEngine implements ISearchEngine { private isCanceled = false; - constructor(private result: () => IRawFileMatch, public config?: IFileQuery) { + constructor(private result: () => IRawFileMatch | null, public config?: IFileQuery) { TestSearchEngine.last = this; } @@ -94,7 +94,7 @@ suite('RawSearchService', () => { test('Individual results', async function () { this.timeout(testTimeout); let i = 5; - const Engine = TestSearchEngine.bind(null, () => i-- && rawMatch); + const Engine = TestSearchEngine.bind(null, () => i-- ? rawMatch : null); const service = new RawSearchService(); let results = 0; @@ -114,7 +114,7 @@ suite('RawSearchService', () => { test('Batch results', async function () { this.timeout(testTimeout); let i = 25; - const Engine = TestSearchEngine.bind(null, () => i-- && rawMatch); + const Engine = TestSearchEngine.bind(null, () => i-- ? rawMatch : null); const service = new RawSearchService(); const results: number[] = []; @@ -137,7 +137,7 @@ suite('RawSearchService', () => { this.timeout(testTimeout); const uriPath = '/some/where'; let i = 25; - const Engine = TestSearchEngine.bind(null, () => i-- && rawMatch); + const Engine = TestSearchEngine.bind(null, () => i-- ? rawMatch : null); const service = new RawSearchService(); function fileSearch(config: IFileQuery, batchSize: number): Event { @@ -157,7 +157,11 @@ suite('RawSearchService', () => { } const progressResults: any[] = []; - const onProgress = (match: IFileMatch) => { + const onProgress = (match: ISearchProgressItem) => { + if (!isFileMatch(match)) { + return; + } + assert.strictEqual(match.resource.path, uriPath); progressResults.push(match); }; @@ -232,7 +236,7 @@ suite('RawSearchService', () => { basename: relativePath, size: 3 })); - const Engine = TestSearchEngine.bind(null, () => matches.shift()); + const Engine = TestSearchEngine.bind(null, () => matches.shift()!); const service = new RawSearchService(); const results: any[] = []; @@ -258,7 +262,7 @@ suite('RawSearchService', () => { test('Sorted result batches', async function () { this.timeout(testTimeout); let i = 25; - const Engine = TestSearchEngine.bind(null, () => i-- && rawMatch); + const Engine = TestSearchEngine.bind(null, () => i-- ? rawMatch : null); const service = new RawSearchService(); const results: number[] = []; @@ -291,7 +295,7 @@ suite('RawSearchService', () => { basename: relativePath, size: 3 })); - const Engine = TestSearchEngine.bind(null, () => matches.shift()); + const Engine = TestSearchEngine.bind(null, () => matches.shift()!); const service = new RawSearchService(); const results: any[] = []; diff --git a/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts b/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts index bb345f5c14..d8dd09b803 100644 --- a/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts +++ b/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts @@ -28,13 +28,13 @@ suite('RipgrepTextSearchEngine', () => { }); test('fixRegexNewline', () => { - function testFixRegexNewline([inputReg, testStr, shouldMatch]: [string, string, boolean]): void { + function testFixRegexNewline([inputReg, testStr, shouldMatch]: readonly [string, string, boolean]): void { const fixed = fixRegexNewline(inputReg); const reg = new RegExp(fixed); assert.equal(reg.test(testStr), shouldMatch, `${inputReg} => ${reg}, ${testStr}, ${shouldMatch}`); } - [ + ([ ['foo', 'foo', true], ['foo\\n', 'foo\r\n', true], @@ -47,17 +47,17 @@ suite('RipgrepTextSearchEngine', () => { ['foo\\n+abc', 'foo\r\nabc', true], ['foo\\n+abc', 'foo\n\n\nabc', true], - ].forEach(testFixRegexNewline); + ] as const).forEach(testFixRegexNewline); }); test('fixNewline', () => { - function testFixNewline([inputReg, testStr, shouldMatch = true]: [string, string, boolean]): void { + function testFixNewline([inputReg, testStr, shouldMatch = true]: readonly [string, string, boolean?]): void { const fixed = fixNewline(inputReg); const reg = new RegExp(fixed); assert.equal(reg.test(testStr), shouldMatch, `${inputReg} => ${reg}, ${testStr}, ${shouldMatch}`); } - [ + ([ ['foo', 'foo'], ['foo\n', 'foo\r\n'], @@ -68,7 +68,7 @@ suite('RipgrepTextSearchEngine', () => { ['foo\nbarc', 'foobar', false], ['foobar', 'foo\nbar', false], - ].forEach(testFixNewline); + ] as const).forEach(testFixNewline); }); suite('RipgrepParser', () => { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 39f4fff7d4..1c639c8ec5 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -13,7 +13,7 @@ import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMet import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IUntitledTextEditorService, IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; @@ -570,9 +570,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Untitled without associated file path const mode = model.getMode(); if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text - suggestedFilename = suggestFilename(mode, model.getName()); + suggestedFilename = suggestFilename(mode, model.name); } else { - suggestedFilename = model.getName(); + suggestedFilename = model.name; } } } @@ -595,7 +595,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Untitled if (resource.scheme === Schemas.untitled) { - const model = this.untitled.exists(resource) ? await this.untitled.resolve({ untitledResource: resource }) : undefined; + const model = this.untitled.get(resource); if (model) { return model.revert(options); } diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index 3c7fed2897..a66d61f1d9 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -316,23 +316,25 @@ suite('Themes - TokenStyleResolving', () => { test('super type', async () => { getTokenClassificationRegistry().registerTokenType('myTestInterface', 'A type just for testing', 'interface'); + getTokenClassificationRegistry().registerTokenType('myTestSubInterface', 'A type just for testing', 'myTestInterface'); + try { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); themeData.setCustomTokenStyleRules({ - 'type': '#ff0000', - 'interface': { fontStyle: 'italic' }, - 'type.static': { fontStyle: 'bold' } + 'interface': '#ff0000', + 'myTestInterface': { fontStyle: 'italic' }, + 'interface.static': { fontStyle: 'bold' } }); - assertTokenStyles(themeData, { 'myTestInterface': ts('#ff0000', { italic: true }) }); - assertTokenStyles(themeData, { 'myTestInterface.static': ts('#ff0000', { italic: true, bold: true }) }); + assertTokenStyles(themeData, { 'myTestSubInterface': ts('#ff0000', { italic: true }) }); + assertTokenStyles(themeData, { 'myTestSubInterface.static': ts('#ff0000', { italic: true, bold: true }) }); themeData.setCustomTokenStyleRules({ - 'type': '#ff0000', - 'interface': { foreground: '#ff00ff', fontStyle: 'italic' } + 'interface': '#ff0000', + 'myTestInterface': { foreground: '#ff00ff', fontStyle: 'italic' } }); - assertTokenStyles(themeData, { 'myTestInterface': ts('#ff00ff', { italic: true }) }); + assertTokenStyles(themeData, { 'myTestSubInterface': ts('#ff00ff', { italic: true }) }); } finally { getTokenClassificationRegistry().deregisterTokenType('myTestInterface'); } diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts similarity index 97% rename from src/vs/workbench/common/editor/untitledTextEditorInput.ts rename to src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index adb883fec1..88812a7e89 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextResourceEditorInput } from 'vs/workbench/common/editor'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter } from 'vs/base/common/event'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -53,6 +53,10 @@ export class UntitledTextEditorInput extends TextResourceEditorInput implements } } + get model(): UntitledTextEditorModel | undefined { + return this.cachedModel; + } + get hasAssociatedFilePath(): boolean { return this._hasAssociatedFilePath; } diff --git a/src/vs/workbench/common/editor/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts similarity index 90% rename from src/vs/workbench/common/editor/untitledTextEditorModel.ts rename to src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 6598840cdd..d89e95f066 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -21,7 +21,13 @@ import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; import { ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; -export interface IUntitledTextEditorModel extends ITextEditorModel, IModeSupport, IEncodingSupport, IWorkingCopy { } +export interface IUntitledTextEditorModel extends ITextEditorModel, IModeSupport, IEncodingSupport, IWorkingCopy { + + /** + * Wether this untitled text model has an associated file path. + */ + readonly hasAssociatedFilePath: boolean; +} export class UntitledTextEditorModel extends BaseTextEditorModel implements IUntitledTextEditorModel { @@ -55,6 +61,8 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } private dirty = false; + private ignoreDirtyOnModelContentChange = false; + private versionId = 0; private configuredEncoding: string | undefined; @@ -124,6 +132,18 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } } + setValue(value: string, ignoreDirty?: boolean): void { + if (ignoreDirty) { + this.ignoreDirtyOnModelContentChange = true; + } + + try { + this.updateTextEditorModel(createTextBufferFactory(value)); + } finally { + this.ignoreDirtyOnModelContentChange = false; + } + } + isReadonly(): boolean { return false; } @@ -214,15 +234,17 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private onModelContentChanged(model: ITextModel, e: IModelContentChangedEvent): void { this.versionId++; - // mark the untitled text editor as non-dirty once its content becomes empty and we do - // not have an associated path set. we never want dirty indicator in that case. - if (!this.hasAssociatedFilePath && model.getLineCount() === 1 && model.getLineContent(1) === '') { - this.setDirty(false); - } + if (!this.ignoreDirtyOnModelContentChange) { + // mark the untitled text editor as non-dirty once its content becomes empty and we do + // not have an associated path set. we never want dirty indicator in that case. + if (!this.hasAssociatedFilePath && model.getLineCount() === 1 && model.getLineContent(1) === '') { + this.setDirty(false); + } - // turn dirty otherwise - else { - this.setDirty(true); + // turn dirty otherwise + else { + this.setDirty(true); + } } // Check for name change if first line changed in the range of 0-FIRST_LINE_NAME_MAX_LENGTH columns diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 405724ea96..afacca47d5 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -5,7 +5,8 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { UntitledTextEditorModel, IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { IFilesConfiguration } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; @@ -13,7 +14,6 @@ import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { UntitledTextEditorModel, IUntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; export const IUntitledTextEditorService = createDecorator('untitledTextEditorService'); @@ -85,16 +85,6 @@ export interface IUntitledTextEditorModelManager { */ readonly onDidDisposeModel: Event; - /** - * Returns if an untitled resource with the given URI exists. - */ - exists(resource: URI): boolean; - - /** - * Returns an existing untitled input if already created before. - */ - get(resource: URI): UntitledTextEditorInput | undefined; - /** * Creates a new untitled input with the provided options. If the `untitledResource` * property is provided and the untitled input exists, it will return that existing @@ -104,6 +94,11 @@ export interface IUntitledTextEditorModelManager { create(options?: INewUntitledTextEditorWithAssociatedResourceOptions): UntitledTextEditorInput; create(options?: IExistingUntitledTextEditorOptions): UntitledTextEditorInput; + /** + * Returns an existing untitled model if already created before. + */ + get(resource: URI): IUntitledTextEditorModel | undefined; + /** * Resolves an untitled editor model from the provided options. If the `untitledResource` * property is provided and the untitled input exists, it will return that existing @@ -112,11 +107,6 @@ export interface IUntitledTextEditorModelManager { resolve(options?: INewUntitledTextEditorOptions): Promise; resolve(options?: INewUntitledTextEditorWithAssociatedResourceOptions): Promise; resolve(options?: IExistingUntitledTextEditorOptions): Promise; - - /** - * A check to find out if a untitled resource has a file path associated or not. - */ - hasAssociatedFilePath(resource: URI): boolean; } export interface IUntitledTextEditorService extends IUntitledTextEditorModelManager { @@ -141,7 +131,6 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe readonly onDidChangeLabel = this._onDidChangeLabel.event; private readonly mapResourceToInput = new ResourceMap(); - private readonly mapResourceToAssociatedFilePath = new ResourceMap(); constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -150,12 +139,8 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe super(); } - exists(resource: URI): boolean { - return this.mapResourceToInput.has(resource); - } - - get(resource: URI): UntitledTextEditorInput | undefined { - return this.mapResourceToInput.get(resource); + get(resource: URI): UntitledTextEditorModel | undefined { + return this.mapResourceToInput.get(resource)?.model; } resolve(options?: IInternalUntitledTextEditorOptions): Promise { @@ -229,17 +214,22 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe // Create new input with provided options const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!options.associatedResource, options.mode, options.initialValue, options.encoding); - const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(input.getResource())); - const labelListener = input.onDidChangeLabel(() => this._onDidChangeLabel.fire(input.getResource())); - const encodingListener = input.onDidModelChangeEncoding(() => this._onDidChangeEncoding.fire(input.getResource())); - const disposeListener = input.onDispose(() => this._onDidDisposeModel.fire(input.getResource())); + this.register(input); + + return input; + } + + private register(editor: UntitledTextEditorInput): void { + const dirtyListener = editor.onDidChangeDirty(() => this._onDidChangeDirty.fire(editor.getResource())); + const labelListener = editor.onDidChangeLabel(() => this._onDidChangeLabel.fire(editor.getResource())); + const encodingListener = editor.onDidModelChangeEncoding(() => this._onDidChangeEncoding.fire(editor.getResource())); + const disposeListener = editor.onDispose(() => this._onDidDisposeModel.fire(editor.getResource())); // Remove from cache on dispose - Event.once(input.onDispose)(() => { + Event.once(editor.onDispose)(() => { // Registry - this.mapResourceToInput.delete(input.getResource()); - this.mapResourceToAssociatedFilePath.delete(input.getResource()); + this.mapResourceToInput.delete(editor.getResource()); // Listeners dirtyListener.dispose(); @@ -249,16 +239,7 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe }); // Add to cache - this.mapResourceToInput.set(untitledResource, input); - if (options.associatedResource) { - this.mapResourceToAssociatedFilePath.set(untitledResource, true); - } - - return input; - } - - hasAssociatedFilePath(resource: URI): boolean { - return this.mapResourceToAssociatedFilePath.has(resource); + this.mapResourceToInput.set(editor.getResource(), editor); } } diff --git a/src/vs/workbench/test/browser/parts/editor/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts similarity index 94% rename from src/vs/workbench/test/browser/parts/editor/untitledTextEditor.test.ts rename to src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index 79a57fbda8..016d720f1b 100644 --- a/src/vs/workbench/test/browser/parts/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import { URI } from 'vs/base/common/uri'; import * as assert from 'assert'; import { join } from 'vs/base/common/path'; @@ -50,17 +51,17 @@ suite('Untitled text editors', () => { const input1 = service.create(); await input1.resolve(); assert.equal(input1, service.create({ untitledResource: input1.getResource() })); - assert.equal(service.get(input1.getResource()), input1); + assert.equal(service.get(input1.getResource()), input1.model); - assert.ok(service.exists(input1.getResource())); - assert.ok(!service.exists(URI.file('testing'))); + assert.ok(service.get(input1.getResource())); + assert.ok(!service.get(URI.file('testing'))); const input2 = service.create(); - assert.equal(service.get(input2.getResource()), input2); + assert.equal(service.get(input2.getResource()), input2.model); // get() - assert.equal(service.get(input1.getResource()), input1); - assert.equal(service.get(input2.getResource()), input2); + assert.equal(service.get(input1.getResource()), input1.model); + assert.equal(service.get(input2.getResource()), input2.model); // revert() await input1.revert(0); @@ -70,7 +71,7 @@ suite('Untitled text editors', () => { // dirty const model = await input2.resolve(); assert.equal(await service.resolve({ untitledResource: input2.getResource() }), model); - assert.ok(service.exists(model.resource)); + assert.ok(service.get(model.resource)); assert.ok(!input2.isDirty()); @@ -99,10 +100,10 @@ suite('Untitled text editors', () => { assert.equal(await input1.revert(0), false); assert.ok(input1.isDisposed()); - assert.ok(!service.exists(input1.getResource())); + assert.ok(!service.get(input1.getResource())); input2.dispose(); - assert.ok(!service.exists(input2.getResource())); + assert.ok(!service.get(input2.getResource())); }); function awaitDidChangeDirty(service: IUntitledTextEditorService): Promise { @@ -115,12 +116,28 @@ suite('Untitled text editors', () => { }); } + test('setValue()', async () => { + const service = accessor.untitledTextEditorService; + const untitled = service.create(); + + const model = await untitled.resolve(); + + model.setValue('not dirty', true); + assert.ok(!model.isDirty()); + + model.setValue('dirty'); + assert.ok(model.isDirty()); + + untitled.dispose(); + model.dispose(); + }); + test('associated resource is dirty', () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); const untitled = service.create({ associatedResource: file }); - assert.ok(service.hasAssociatedFilePath(untitled.getResource())); + assert.ok(untitled.hasAssociatedFilePath); assert.equal(untitled.isDirty(), true); untitled.dispose(); @@ -165,7 +182,7 @@ suite('Untitled text editors', () => { const file = URI.file(join('C:\\', '/foo/file44.txt')); const model4 = await service.create({ associatedResource: file }).resolve(); - assert.ok(service.hasAssociatedFilePath(model4.resource)); + assert.ok(model4.hasAssociatedFilePath); assert.ok(model4.isDirty()); model1.dispose(); diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index 3d4d33e399..16c316da3b 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -11,7 +11,6 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourcePropertiesService, ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; class UserDataSyncUtilService implements IUserDataSyncUtilService { @@ -22,13 +21,8 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { @ITextModelService private readonly textModelService: ITextModelService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, - @IConfigurationService private readonly configurationService: IConfigurationService, ) { } - public async updateConfigurationValue(key: string, value: any): Promise { - await this.configurationService.updateValue(key, value, ConfigurationTarget.USER); - } - public async resolveUserBindings(userBindings: string[]): Promise> { const keys: IStringDictionary = {}; for (const userbinding of userBindings) { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 76ce1352fa..c3f78516ef 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -16,6 +16,7 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ private readonly channel: IChannel; + readonly resourceKey = 'settings'; readonly source = SyncSource.Settings; private _status: SyncStatus = SyncStatus.Uninitialized; @@ -63,11 +64,6 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ return this.channel.call('stop'); } - async restart(): Promise { - const status = await this.channel.call('restart'); - await this.updateStatus(status); - } - resetLocal(): Promise { return this.channel.call('resetLocal'); } @@ -76,10 +72,6 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ return this.channel.call('hasPreviouslySynced'); } - hasRemoteData(): Promise { - return this.channel.call('hasRemoteData'); - } - hasLocalData(): Promise { return this.channel.call('hasLocalData'); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 39b9e01fe8..6dd4c481d7 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -24,8 +24,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } - private _conflictsSource: SyncSource | null = null; - get conflictsSource(): SyncSource | null { return this._conflictsSource; } + private _conflictsSources: SyncSource[] = []; + get conflictsSources(): SyncSource[] { return this._conflictsSources; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; constructor( @ISharedProcessService sharedProcessService: ISharedProcessService @@ -41,20 +43,18 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return userDataSyncChannel.listen(event, arg); } }; - this.channel.call('_getInitialStatus').then(status => { + this.channel.call<[SyncStatus, SyncSource[]]>('_getInitialData').then(([status, conflicts]) => { this.updateStatus(status); + this.updateConflicts(conflicts); this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); }); + this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); } pull(): Promise { return this.channel.call('pull'); } - push(): Promise { - return this.channel.call('push'); - } - sync(): Promise { return this.channel.call('sync'); } @@ -75,37 +75,24 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('stop'); } - async restart(): Promise { - const status = await this.channel.call('restart'); - await this.updateStatus(status); - } - - hasPreviouslySynced(): Promise { - return this.channel.call('hasPreviouslySynced'); - } - - hasRemoteData(): Promise { - return this.channel.call('hasRemoteData'); - } - - hasLocalData(): Promise { - return this.channel.call('hasLocalData'); - } - getRemoteContent(source: SyncSource, preview: boolean): Promise { return this.channel.call('getRemoteContent', [source, preview]); } - isFirstTimeSyncAndHasUserData(): Promise { - return this.channel.call('isFirstTimeSyncAndHasUserData'); + isFirstTimeSyncWithMerge(): Promise { + return this.channel.call('isFirstTimeSyncWithMerge'); } private async updateStatus(status: SyncStatus): Promise { - this._conflictsSource = await this.channel.call('getConflictsSource'); this._status = status; this._onDidChangeStatus.fire(status); } + private async updateConflicts(conflicts: SyncSource[]): Promise { + this._conflictsSources = conflicts; + this._onDidChangeConflicts.fire(conflicts); + } + } registerSingleton(IUserDataSyncService, UserDataSyncService); diff --git a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts index ddc22fedf6..dc630c7238 100644 --- a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts @@ -12,6 +12,7 @@ import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers' import { mock } from 'vs/workbench/test/browser/api/mock'; import { Emitter, Event } from 'vs/base/common/event'; import { NullLogService } from 'vs/platform/log/common/log'; +import type * as vscode from 'vscode'; suite('ExtHostDiagnostics', () => { @@ -96,7 +97,7 @@ suite('ExtHostDiagnostics', () => { assert.throws(() => array.pop()); assert.throws(() => array[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil')); - collection.forEach((uri, array: readonly Diagnostic[]) => { + collection.forEach((uri: URI, array: readonly vscode.Diagnostic[]): any => { assert.throws(() => (array as Diagnostic[]).length = 0); assert.throws(() => (array as Diagnostic[]).pop()); assert.throws(() => (array as Diagnostic[])[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil')); diff --git a/src/vs/workbench/test/browser/api/testRPCProtocol.ts b/src/vs/workbench/test/browser/api/testRPCProtocol.ts index 47154800c6..4a5829d377 100644 --- a/src/vs/workbench/test/browser/api/testRPCProtocol.ts +++ b/src/vs/workbench/test/browser/api/testRPCProtocol.ts @@ -79,12 +79,13 @@ export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService { private _createProxy(proxyId: string): T { let handler = { - get: (target: any, name: string) => { - if (!target[name] && name.charCodeAt(0) === CharCode.DollarSign) { + get: (target: any, name: PropertyKey) => { + if (typeof name === 'string' && !target[name] && name.charCodeAt(0) === CharCode.DollarSign) { target[name] = (...myArgs: any[]) => { return this._remoteCall(proxyId, name, myArgs); }; } + return target[name]; } }; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 58187477e8..c059c1ca1a 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -93,6 +93,7 @@ export import TestContextService = CommonWorkbenchTestServices.TestContextServic export import TestStorageService = CommonWorkbenchTestServices.TestStorageService; export import TestWorkingCopyService = CommonWorkbenchTestServices.TestWorkingCopyService; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; +import { Direction } from 'vs/base/browser/ui/grid/grid'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -225,6 +226,7 @@ export class TestAccessibilityService implements IAccessibilityService { isScreenReaderOptimized(): boolean { return false; } alwaysUnderlineAccessKeys(): Promise { return Promise.resolve(false); } setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { } + getAccessibilitySupport(): AccessibilitySupport { return AccessibilitySupport.Unknown; } } export class TestDecorationsService implements IDecorationsService { @@ -361,6 +363,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { registerPart(part: Part): void { } isWindowMaximized() { return false; } updateWindowMaximizedState(maximized: boolean): void { } + getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined { return undefined; } } let activeViewlet: Viewlet = {} as any; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 052f6bc5f5..363ebeb5c5 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -112,7 +112,10 @@ import { IDownloadService } from 'vs/platform/download/common/download'; import { DownloadService } from 'vs/platform/download/common/downloadService'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; +registerSingleton(IUserDataSyncEnablementService, UserDataSyncEnablementService); registerSingleton(IGlobalExtensionEnablementService, GlobalExtensionEnablementService); registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(IContextViewService, ContextViewService, true); @@ -306,7 +309,7 @@ import 'vs/workbench/contrib/output/browser/outputView'; // Terminal import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; -import 'vs/workbench/contrib/terminal/browser/terminalPanel'; +import 'vs/workbench/contrib/terminal/browser/terminalView'; // Relauncher import 'vs/workbench/contrib/relauncher/browser/relauncher.contribution'; diff --git a/test/README.md b/test/README.md index 81bc286241..445945d7c9 100644 --- a/test/README.md +++ b/test/README.md @@ -3,34 +3,7 @@ ## Contents This folder contains the various test runners for VSCode. Please refer to the documentation within for how to run them: -* `unit`: our suite of unit tests -* `integration`: our suite of API tests -* `smoke`: our suite of automated UI tests +* `unit`: our suite of unit tests ([README](unit/README.md)) +* `integration`: our suite of API tests ([README](integration/browser/README.md)) +* `smoke`: our suite of automated UI tests ([README](smoke/README.md)) * `ui`: our suite of manual UI tests - - - -### Unit Tests (Electron-runner) - -``` -./scripts/test.[sh|bat] -``` - -All unit tests are run inside a electron-browser environment which access to DOM and Nodejs api. This is the closest to the enviroment in which VS Code itself ships. Notes: - -- use the `--debug` to see an electron window with dev tools which allows for debugging -- to run only a subset of tests use the `--run` or `--glob` options - -### Unit Tests (Browser-runner) - -``` -yarn test-browser --browser webkit --browser chromium -``` - -Unit tests from layers `common` and `browser` are run inside `chromium`, `webkit`, and (soon’ish) `firefox` (using playwright). This complements our electron-based unit test runner and adds more coverage of supported platforms. Notes: - -- these tests are part of the continuous build, that means you might have test failures that only happen with webkit on _windows_ or _chromium_ on linux -- you can these tests locally via yarn `test-browser --browser chromium --browser webkit` -- to debug, open `/test/unit/browser/renderer.html` inside a browser and use the `?m=`-query to specify what AMD module to load, e.g `file:///Users/jrieken/Code/vscode/test/unit/browser/renderer.html?m=vs/base/test/common/strings.test` runs all tests from `strings.test.ts` -- to run only a subset of tests use the `--run` or `--glob` options - diff --git a/test/integration/browser/README.md b/test/integration/browser/README.md index 529130a7f7..dd8b6b74d4 100644 --- a/test/integration/browser/README.md +++ b/test/integration/browser/README.md @@ -1,13 +1,19 @@ -# VS Code Integration test +# Integration test -### Run +## Compile -```bash +Make sure to run the following command to compile and install dependencies: -# Dev (Electron) -scripts/test-integration.sh + yarn --cwd test/integration/browser -# Dev (Web) -node test/integration/browser/out/index.js +## Run (inside Electron) -``` + scripts/test-integration.[sh|bat] + +All integration tests run in an Electron instance. You can specify to run the tests against a real build by setting the environment variables `INTEGRATION_TEST_ELECTRON_PATH` and `VSCODE_REMOTE_SERVER_PATH` (if you want to include remote tests). + +## Run (inside browser) + + resources/server/test/test-web-integration.[sh|bat] --browser [chromium|webkit] + +All integration tests run in a browser instance as specified by the command line arguments. diff --git a/test/integration/browser/package.json b/test/integration/browser/package.json index 2b9c7cd2c4..d43ee230c4 100644 --- a/test/integration/browser/package.json +++ b/test/integration/browser/package.json @@ -13,6 +13,8 @@ "@types/tmp": "^0.1.0", "rimraf": "^2.6.1", "tmp": "0.0.33", - "typescript": "3.7.5" + "tree-kill": "1.2.2", + "typescript": "3.7.5", + "vscode-uri": "2.1.1" } } diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index cd271eb738..622065f96c 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -9,11 +9,13 @@ import * as playwright from 'playwright'; import * as url from 'url'; import * as tmp from 'tmp'; import * as rimraf from 'rimraf'; +import { URI } from 'vscode-uri'; +import * as kill from 'tree-kill'; const optimist = require('optimist') - .describe('workspacePath', 'path to the workspace to open in the test').string() - .describe('extensionDevelopmentPath', 'path to the extension to test').string() - .describe('extensionTestsPath', 'path to the extension tests').string() + .describe('workspacePath', 'path to the workspace to open in the test').string('workspacePath') + .describe('extensionDevelopmentPath', 'path to the extension to test').string('extensionDevelopmentPath') + .describe('extensionTestsPath', 'path to the extension tests').string('extensionTestsPath') .describe('debug', 'do not run browsers headless').boolean('debug') .describe('browser', 'browser in which integration tests should run').string('browser').default('browser', 'chromium') .describe('help', 'show the help').alias('help', 'h'); @@ -26,22 +28,22 @@ if (optimist.argv.help) { const width = 1200; const height = 800; -async function runTestsInBrowser(browserType: string, endpoint: string): Promise { +async function runTestsInBrowser(browserType: string, endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise { const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug) }); const page = (await browser.defaultContext().pages())[0]; await page.setViewport({ width, height }); - const host = url.parse(endpoint).host; + const host = endpoint.host; const protocol = 'vscode-remote'; - const testWorkspaceUri = url.format({ pathname: path.resolve(optimist.argv.workspacePath), protocol, host, slashes: true }); - const testExtensionUri = url.format({ pathname: path.resolve(optimist.argv.extensionDevelopmentPath), protocol, host, slashes: true }); - const testFilesUri = url.format({ pathname: path.resolve(optimist.argv.extensionTestsPath), protocol, host, slashes: true }); + const testWorkspaceUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.workspacePath)).path, protocol, host, slashes: true }); + const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true }); + const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true }); const folderParam = testWorkspaceUri; const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"]]`; - await page.goto(`${endpoint}&folder=${folderParam}&payload=${payloadParam}`); + await page.goto(`${endpoint.href}&folder=${folderParam}&payload=${payloadParam}`); await page.exposeFunction('codeAutomationLog', (type: string, args: any[]) => { console[type](...args); @@ -50,13 +52,30 @@ async function runTestsInBrowser(browserType: string, endpoint: string): Promise page.on('console', async (msg: playwright.ConsoleMessage) => { const msgText = msg.text(); if (msgText.indexOf('vscode:exit') >= 0) { - await browser.close(); + try { + await browser.close(); + } catch (error) { + console.error(`Error when closing browser: ${error}`); + } + + try { + await pkill(server.pid); + } catch (error) { + console.error(`Error when killing server process tree: ${error}`); + } + process.exit(msgText === 'vscode:exit 0' ? 0 : 1); } }); } -async function launchServer(): Promise { +function pkill(pid: number): Promise { + return new Promise((c, e) => { + kill(pid, error => error ? e(error) : c()); + }); +} + +async function launchServer(): Promise<{ endpoint: url.UrlWithStringQuery, server: cp.ChildProcess }> { // Ensure a tmp user-data-dir is used for the tests const tmpDir = tmp.dirSync({ prefix: 't' }); @@ -91,26 +110,23 @@ async function launchServer(): Promise { serverProcess?.stdout?.on('data', data => console.log(`Server stdout: ${data}`)); } - function teardownServer() { - if (serverProcess) { - serverProcess.kill(); - } - } - - process.on('exit', teardownServer); - process.on('SIGINT', teardownServer); - process.on('SIGTERM', teardownServer); + process.on('exit', () => serverProcess.kill()); + process.on('SIGINT', () => serverProcess.kill()); + process.on('SIGTERM', () => serverProcess.kill()); return new Promise(c => { serverProcess?.stdout?.on('data', data => { const matches = data.toString('ascii').match(/Web UI available at (.+)/); if (matches !== null) { - c(matches[1]); + c({ endpoint: url.parse(matches[1]), server: serverProcess }); } }); }); } -launchServer().then(async endpoint => { - return runTestsInBrowser(optimist.argv.browser, endpoint); -}, console.error); +launchServer().then(async ({ endpoint, server }) => { + return runTestsInBrowser(optimist.argv.browser, endpoint, server); +}, error => { + console.error(error); + process.exit(1); +}); diff --git a/test/integration/browser/yarn.lock b/test/integration/browser/yarn.lock index 647f08c5ce..35884dcfc5 100644 --- a/test/integration/browser/yarn.lock +++ b/test/integration/browser/yarn.lock @@ -137,11 +137,21 @@ tmp@0.0.33: dependencies: os-tmpdir "~1.0.2" +tree-kill@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + typescript@3.7.5: version "3.7.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== +vscode-uri@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.1.tgz#5aa1803391b6ebdd17d047f51365cf62c38f6e90" + integrity sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A== + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" diff --git a/test/smoke/README.md b/test/smoke/README.md index ddd05e7eb6..8b41d05ed0 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -13,13 +13,13 @@ yarn --cwd test/automation yarn smoketest # Dev (Web) -yarn smoketest --web --browser +yarn smoketest --web --browser [chromium|firefox|webkit] # Build (Electron) yarn smoketest --build --stable-build # Build (Web - read instructions below) -yarn smoketest --build --web --browser +yarn smoketest --build --web --browser [chromium|firefox|webkit] # Remote (Electron) yarn smoketest --build --remote diff --git a/test/unit/README.md b/test/unit/README.md index 206f4baccf..4172285834 100644 --- a/test/unit/README.md +++ b/test/unit/README.md @@ -2,29 +2,23 @@ ## Run (inside Electron) -The best way to run the unit tests is from the terminal. To make development changes to unit tests you need to be running `yarn watch`. See [Development Workflow](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#incremental-build) for more details. From the `vscode` folder run: + ./scripts/test.[sh|bat] -**OS X and Linux** - - ./scripts/test.sh - -**Windows** - - scripts\test +All unit tests are run inside a electron-browser environment which access to DOM and Nodejs api. This is the closest to the enviroment in which VS Code itself ships. Notes: +- use the `--debug` to see an electron window with dev tools which allows for debugging +- to run only a subset of tests use the `--run` or `--glob` options ## Run (inside browser) -You can run tests inside a browser instance too: + yarn test-browser --browser webkit --browser chromium -**OS X and Linux** - - node test/unit/browser/index.js - -**Windows** - - node test\unit\browser\index.js +Unit tests from layers `common` and `browser` are run inside `chromium`, `webkit`, and (soon’ish) `firefox` (using playwright). This complements our electron-based unit test runner and adds more coverage of supported platforms. Notes: +- these tests are part of the continuous build, that means you might have test failures that only happen with webkit on _windows_ or _chromium_ on linux +- you can these tests locally via yarn `test-browser --browser chromium --browser webkit` +- to debug, open `/test/unit/browser/renderer.html` inside a browser and use the `?m=`-query to specify what AMD module to load, e.g `file:///Users/jrieken/Code/vscode/test/unit/browser/renderer.html?m=vs/base/test/common/strings.test` runs all tests from `strings.test.ts` +- to run only a subset of tests use the `--run` or `--glob` options ## Run (with node) diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index 143dc8ae5b..6e1703c708 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -18,7 +18,7 @@ const playwright = require('playwright'); const defaultReporterName = process.platform === 'win32' ? 'list' : 'spec'; const optimist = require('optimist') // .describe('grep', 'only run tests matching ').alias('grep', 'g').alias('grep', 'f').string('grep') - // .describe('build', 'run with build output (out-build)').boolean('build') + .describe('build', 'run with build output (out-build)').boolean('build') .describe('run', 'only run tests matching ').string('run') .describe('glob', 'only run tests matching ').string('glob') .describe('debug', 'do not run browsers headless').boolean('debug') @@ -122,6 +122,9 @@ async function runTestsInBrowser(testModules, browserType) { const browser = await playwright[browserType].launch({ headless: !Boolean(argv.debug) }); const page = (await browser.defaultContext().pages())[0] const target = url.pathToFileURL(path.join(__dirname, 'renderer.html')); + if (argv.build) { + target.search = `?build=true`; + } await page.goto(target.href); const emitter = new events.EventEmitter(); diff --git a/test/unit/browser/renderer.html b/test/unit/browser/renderer.html index 2f46b408f4..61b2de146a 100644 --- a/test/unit/browser/renderer.html +++ b/test/unit/browser/renderer.html @@ -37,17 +37,24 @@ }); + + + @@ -104,19 +111,19 @@ window.loadAndRun = async function loadAndRun(modules, manual = false) { // load - // await Promise.all(modules.map(module => new Promise((resolve, reject) =>{ - // require([module], resolve, err => { - // console.log("BAD " + module + JSON.stringify(err, undefined, '\t')); - // // console.log(module); - // resolve({}); - // }); - // }))); - await new Promise((resolve, reject) => { - require(modules, resolve, err => { - console.log(err); - reject(err); + await Promise.all(modules.map(module => new Promise((resolve, reject) => { + require([module], resolve, err => { + console.log("BAD " + module + JSON.stringify(err, undefined, '\t')); + // console.log(module); + resolve({}); }); - }); + }))); + // await new Promise((resolve, reject) => { + // require(modules, resolve, err => { + // console.log(err); + // reject(err); + // }); + // }); // run return new Promise((resolve, reject) => { @@ -127,7 +134,6 @@ }); } - const modules = new URL(window.location.href).searchParams.getAll('m'); if (Array.isArray(modules) && modules.length > 0) { console.log('MANUALLY running tests', modules); diff --git a/yarn.lock b/yarn.lock index b6b97b97c1..df4c9d386f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9859,10 +9859,10 @@ vscode-proxy-agent@^0.5.2: https-proxy-agent "^2.2.3" socks-proxy-agent "^4.0.1" -vscode-ripgrep@^1.5.7: - version "1.5.7" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.7.tgz#acb6b548af488a4bca5d0f1bb5faf761343289ce" - integrity sha512-/Vsz/+k8kTvui0q3O74pif9FK0nKopgFTiGNVvxicZANxtSA8J8gUE9GQ/4dpi7D/2yI/YVORszwVskFbz46hQ== +vscode-ripgrep@^1.5.8: + version "1.5.8" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.8.tgz#32cb33da6d1a9ca8f5de8c2813ed5114fd55fc11" + integrity sha512-l6Pv/t1Jk63RU+kEkMO04XxnNRYdyzuesizj9AzFpcfrUxxpAjEJBK1qO9Mov30UUGZl7uDUBn+uCv9koaHPPA== vscode-sqlite3@4.0.9: version "4.0.9" @@ -10177,10 +10177,10 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0.tgz#a7beadb3caa7330eb31fb1f17d92de25537684a1" - integrity sha512-g07qb/Z4aSfrQ25e6Z6rz6KiExm2DvesQXkx+eA715VABBr5VM/9Jf0INoCiDSYy/nn7rpna+kXiGVJejIffKg== +xterm-addon-search@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.5.0.tgz#cd3a2f8056084c28e236d4e732da37682010bcc2" + integrity sha512-zLVqVTrg5w2nk9fRj3UuVKCPo/dmFe/cLf3EM9Is5Dm6cgOoXmeo9eq2KgD8A0gquAflTFTf0ya2NaFmShHwyg== xterm-addon-unicode11@0.1.1: version "0.1.1"