diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6c5d2999898..f64aa7df15c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,14 +24,14 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: javascript # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -45,4 +45,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/deep-classifier-runner.yml b/.github/workflows/deep-classifier-runner.yml index bae2eeb1967..a966318ddef 100644 --- a/.github/workflows/deep-classifier-runner.yml +++ b/.github/workflows/deep-classifier-runner.yml @@ -46,7 +46,7 @@ jobs: uses: ./actions/classifier-deep/apply/apply-labels with: configPath: classifier - allowLabels: "needs more info|new release|error-telemetry|*english-please|translation-required" + allowLabels: "info-needed|new release|error-telemetry|*english-please|translation-required" appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} manifestDbConnectionString: ${{secrets.MANIFEST_DB_CONNECTION_STRING}} token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} diff --git a/.github/workflows/english-please.yml b/.github/workflows/english-please.yml index da8ca03ddd8..2f24b039125 100644 --- a/.github/workflows/english-please.yml +++ b/.github/workflows/english-please.yml @@ -24,6 +24,6 @@ jobs: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}} nonEnglishLabel: "*english-please" - needsMoreInfoLabel: "needs more info" + needsMoreInfoLabel: "info-needed" translatorRequestedLabelPrefix: "translation-required-" translatorRequestedLabelColor: "c29cff" diff --git a/.github/workflows/needs-more-info-closer.yml b/.github/workflows/needs-more-info-closer.yml index 5e1f5b5c548..65805be0d51 100644 --- a/.github/workflows/needs-more-info-closer.yml +++ b/.github/workflows/needs-more-info-closer.yml @@ -1,4 +1,4 @@ -name: Needs More Info Closer +name: info-needed Closer on: schedule: - cron: 20 11 * * * # 4:20am Redmond @@ -17,12 +17,12 @@ jobs: ref: stable - name: Install Actions run: npm install --production --prefix ./actions - - name: Run Needs More Info Closer + - name: Run info-needed Closer uses: ./actions/needs-more-info-closer with: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} - label: needs more info + label: info-needed closeDays: 7 additionalTeam: "cleidigh|usernamehw|gjsjohnmurray|IllusionMH" closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" diff --git a/.github/workflows/on-label.yml b/.github/workflows/on-label.yml index 25f7a67c7a1..ae4c6f1419c 100644 --- a/.github/workflows/on-label.yml +++ b/.github/workflows/on-label.yml @@ -85,6 +85,6 @@ jobs: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}} nonEnglishLabel: "*english-please" - needsMoreInfoLabel: "needs more info" + needsMoreInfoLabel: "info-needed" translatorRequestedLabelPrefix: "translation-required-" translatorRequestedLabelColor: "c29cff" diff --git a/.github/workflows/on-open.yml b/.github/workflows/on-open.yml index af70c86caa0..3d800514a94 100644 --- a/.github/workflows/on-open.yml +++ b/.github/workflows/on-open.yml @@ -59,6 +59,6 @@ jobs: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}} nonEnglishLabel: "*english-please" - needsMoreInfoLabel: "needs more info" + needsMoreInfoLabel: "info-needed" translatorRequestedLabelPrefix: "translation-required-" translatorRequestedLabelColor: "c29cff" diff --git a/.vscode/notebooks/inbox.github-issues b/.vscode/notebooks/inbox.github-issues index ad451a6f91a..10d7fd63e54 100644 --- a/.vscode/notebooks/inbox.github-issues +++ b/.vscode/notebooks/inbox.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$inbox -label:\"needs more info\" sort:created-desc" + "value": "$inbox -label:\"info-needed\" sort:created-desc" }, { "kind": 2, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index c0a6e4403b3..25b9625ef31 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:needs-triage -label:verification-found" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index f50570078e6..f3c7a9c7c91 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -92,7 +92,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"needs more info\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry" + "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry" }, { "kind": 1, @@ -102,7 +102,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode assignee:@me is:open type:issue -label:\"needs more info\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:code-lens -label:color-palette -label:comments -label:config -label:context-keys -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-RTL -label:editor-scrollbar -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:engineering -label:error-list -label:extension-host -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-io -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:gpu -label:grammar -label:grid-view -label:html -label:i18n -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-window -label:ipc -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:L10N -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list -label:live-server -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:network -label:notebook -label:notebook-api -label:notebook-celltoolbar -label:notebook-diff -label:notebook-dnd -label:notebook-folding -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-keybinding -label:notebook-layout -label:notebook-markdown -label:notebook-minimap -label:notebook-multiselect -label:notebook-output -label:notebook-perf -label:notebook-statusbar -label:open-editors -label:opener -label:outline -label:output -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remotehub -label:rename -label:sandbox -label:sash -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview -label:suggest -label:sync-error-handling -label:table -label:tasks -label:telemetry -label:terminal -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-links -label:terminal-local-echo -label:terminal-profiles -label:terminal-reconnection -label:terminal-rendering -label:terminal-tabs -label:terminal-winpty -label:testing -label:themes -label:timeline -label:timeline-git -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typehierarchy -label:typescript -label:undo-redo -label:uri -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-build -label:vscode-website -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -label:workbench-editors -label:workbench-electron -label:workbench-feedback -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom" + "value": "repo:microsoft/vscode assignee:@me is:open type:issue -label:\"info-needed\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:code-lens -label:color-palette -label:comments -label:config -label:context-keys -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-RTL -label:editor-scrollbar -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:engineering -label:error-list -label:extension-host -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-io -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:gpu -label:grammar -label:grid-view -label:html -label:i18n -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-window -label:ipc -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:L10N -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list -label:live-server -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:network -label:notebook -label:notebook-api -label:notebook-celltoolbar -label:notebook-diff -label:notebook-dnd -label:notebook-folding -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-keybinding -label:notebook-layout -label:notebook-markdown -label:notebook-minimap -label:notebook-multiselect -label:notebook-output -label:notebook-perf -label:notebook-statusbar -label:open-editors -label:opener -label:outline -label:output -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remotehub -label:rename -label:sandbox -label:sash -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview -label:suggest -label:sync-error-handling -label:table -label:tasks -label:telemetry -label:terminal -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-links -label:terminal-local-echo -label:terminal-profiles -label:terminal-reconnection -label:terminal-rendering -label:terminal-tabs -label:terminal-winpty -label:testing -label:themes -label:timeline -label:timeline-git -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typehierarchy -label:typescript -label:undo-redo -label:uri -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-build -label:vscode-website -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -label:workbench-editors -label:workbench-electron -label:workbench-feedback -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos assignee:@me is:open label:\"needs more info\"" + "value": "$repos assignee:@me is:open label:\"info-needed\"" }, { "kind": 1, diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 9c691ad41e3..5c36a652b4b 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -31,7 +31,7 @@ This project incorporates components from the projects listed below. The origina 24. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) 25. James-Yu/LaTeX-Workshop version 8.19.1 (https://github.com/James-Yu/LaTeX-Workshop) 26. jeff-hykin/better-c-syntax version 1.13.2 (https://github.com/jeff-hykin/better-c-syntax) -27. jeff-hykin/better-cpp-syntax version 1.15.17 (https://github.com/jeff-hykin/better-cpp-syntax) +27. jeff-hykin/better-cpp-syntax version 1.15.18 (https://github.com/jeff-hykin/better-cpp-syntax) 28. jeff-hykin/better-objc-syntax version 0.2.0 (https://github.com/jeff-hykin/better-objc-syntax) 29. jeff-hykin/better-objcpp-syntax version 0.1.0 (https://github.com/jeff-hykin/better-objcpp-syntax) 30. jlelong/vscode-latex-basics version 1.3.0 (https://github.com/jlelong/vscode-latex-basics) @@ -47,7 +47,7 @@ This project incorporates components from the projects listed below. The origina 40. microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/microsoft/TypeScript-TmLanguage) 41. microsoft/vscode-JSON.tmLanguage (https://github.com/microsoft/vscode-JSON.tmLanguage) 42. microsoft/vscode-markdown-tm-grammar version 1.0.0 (https://github.com/microsoft/vscode-markdown-tm-grammar) -43. microsoft/vscode-mssql version 1.10.1 (https://github.com/microsoft/vscode-mssql) +43. microsoft/vscode-mssql version 1.16.0 (https://github.com/microsoft/vscode-mssql) 44. mmims/language-batchfile version 0.7.6 (https://github.com/mmims/language-batchfile) 45. NVIDIA/cuda-cpp-grammar (https://github.com/NVIDIA/cuda-cpp-grammar) 46. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) @@ -73,7 +73,7 @@ This project incorporates components from the projects listed below. The origina 66. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) 67. Unicode version 12.0.0 (https://home.unicode.org/) 68. vscode-codicons version 0.0.14 (https://github.com/microsoft/vscode-codicons) -69. vscode-logfile-highlighter version 2.11.0 (https://github.com/emilast/vscode-logfile-highlighter) +69. vscode-logfile-highlighter version 2.15.0 (https://github.com/emilast/vscode-logfile-highlighter) 70. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) 71. vscode-win32-app-container-tokens (https://github.com/microsoft/vscode-win32-app-container-tokens) 72. Web Background Synchronization (https://github.com/WICG/background-sync) diff --git a/build/azure-pipelines/linux/product-build-linux-client.yml b/build/azure-pipelines/linux/product-build-linux-client.yml index 1f57307739a..97a9cf31d66 100644 --- a/build/azure-pipelines/linux/product-build-linux-client.yml +++ b/build/azure-pipelines/linux/product-build-linux-client.yml @@ -93,12 +93,21 @@ steps: node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash displayName: Prepare yarn cache flags - - task: Cache@2 - inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - task: Cache@2 + inputs: + key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: Cache@2 + inputs: + key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache - script: | set -e diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index 8f06bcfd09c..8362da25ee5 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -22,174 +22,168 @@ variables: - name: VSCODE_STEP_ON_IT value: false -stages: - - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - stage: MaintainNodeModulesCache - displayName: Maintain node_modules cache - jobs: - - job: MaintainNodeModulesCache - displayName: Maintain node_modules cache - pool: vscode-1es-vscode-linux-20.04 - steps: - - template: product-build-pr-cache.yml - +jobs: - ${{ if ne(variables['VSCODE_CIBUILD'], true) }}: - - stage: Compile + - job: Compile displayName: Compile & Hygiene - jobs: - - job: Compile - displayName: Compile & Hygiene - pool: vscode-1es-vscode-linux-20.04 - variables: - VSCODE_ARCH: x64 - steps: - - template: product-compile.yml - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + steps: + - template: product-compile.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - stage: Test - dependsOn: [] - jobs: - - job: Linuxx64UnitTest - displayName: Linux (Unit Tests) - pool: vscode-1es-vscode-linux-20.04 - # container: vscode-bionic-x64 - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64IntegrationTest - displayName: Linux (Integration Tests) - pool: vscode-1es-vscode-linux-20.04 - # container: vscode-bionic-x64 - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64SmokeTest - displayName: Linux (Smoke Tests) - pool: vscode-1es-vscode-linux-20.04 - # container: vscode-bionic-x64 - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - job: Linuxx64UnitTest + displayName: Linux (Unit Tests) + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false - # - job: macOSUnitTest - # displayName: macOS (Unit Tests) - # pool: - # vmImage: macOS-latest - # timeoutInMinutes: 60 - # variables: - # BUILDSECMON_OPT_IN: true - # VSCODE_ARCH: x64 - # steps: - # - template: darwin/product-build-darwin.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: true - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: false - # - job: macOSIntegrationTest - # displayName: macOS (Integration Tests) - # pool: - # vmImage: macOS-latest - # timeoutInMinutes: 60 - # variables: - # BUILDSECMON_OPT_IN: true - # VSCODE_ARCH: x64 - # steps: - # - template: darwin/product-build-darwin.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: true - # VSCODE_RUN_SMOKE_TESTS: false - # - job: macOSSmokeTest - # displayName: macOS (Smoke Tests) - # pool: - # vmImage: macOS-latest - # timeoutInMinutes: 60 - # variables: - # BUILDSECMON_OPT_IN: true - # VSCODE_ARCH: x64 - # steps: - # - template: darwin/product-build-darwin.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: true + - job: Linuxx64IntegrationTest + displayName: Linux (Integration Tests) + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false - # - job: WindowsUnitTests - # displayName: Windows (Unit Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: true - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: false - # - job: WindowsIntegrationTests - # displayName: Windows (Integration Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: true - # VSCODE_RUN_SMOKE_TESTS: false - # - job: WindowsSmokeTests - # displayName: Windows (Smoke Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: true + - job: Linuxx64SmokeTest + displayName: Linux (Smoke Tests) + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true + + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: + - job: Linuxx64MaintainNodeModulesCache + displayName: Linux (Maintain node_modules cache) + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + steps: + - template: product-build-pr-cache.yml + + # - job: macOSUnitTest + # displayName: macOS (Unit Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: true + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: false + # - job: macOSIntegrationTest + # displayName: macOS (Integration Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: true + # VSCODE_RUN_SMOKE_TESTS: false + # - job: macOSSmokeTest + # displayName: macOS (Smoke Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true + + # - job: WindowsUnitTests + # displayName: Windows (Unit Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: true + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: false + # - job: WindowsIntegrationTests + # displayName: Windows (Integration Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: true + # VSCODE_RUN_SMOKE_TESTS: false + # - job: WindowsSmokeTests + # displayName: Windows (Smoke Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 168cf8a5d00..808d817b815 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -399,6 +399,7 @@ const esbuildMediaScripts = [ 'markdown-language-features/esbuild-preview.js', 'markdown-math/esbuild.js', 'notebook-renderers/esbuild.js', + 'ipynb/esbuild.js', 'simple-browser/esbuild-preview.js', ]; async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 89aa014a0d6..ddcb25483fb 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -486,6 +486,7 @@ const esbuildMediaScripts = [ 'markdown-language-features/esbuild-preview.js', 'markdown-math/esbuild.js', 'notebook-renderers/esbuild.js', + 'ipynb/esbuild.js', 'simple-browser/esbuild-preview.js', ]; diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 7fa1a6519a7..dd949e618f2 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -298,6 +298,10 @@ "name": "vs/workbench/contrib/deprecatedExtensionMigrator", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/bracketPairColorizer2Telemetry", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/offline", "project": "vscode-workbench" diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 9a11c6fd9cc..ff18694436d 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -228,6 +228,12 @@ function getColor(node) { function setColor(node, color) { node.$$$color = color; } +function markNeededSourceFile(node) { + node.$$$neededSourceFile = true; +} +function isNeededSourceFile(node) { + return Boolean(node.$$$neededSourceFile); +} function nodeOrParentIsBlack(node) { while (node) { const color = getColor(node); @@ -353,6 +359,19 @@ function markNodes(ts, languageService, options) { } }); } + /** + * Return the parent of `node` which is an ImportDeclaration + */ + function findParentImportDeclaration(node) { + let _node = node; + do { + if (ts.isImportDeclaration(_node)) { + return _node; + } + _node = _node.parent; + } while (_node); + return null; + } function enqueue_gray(node) { if (nodeOrParentIsBlack(node) || getColor(node) === 1 /* NodeColor.Gray */) { return; @@ -420,6 +439,8 @@ function markNodes(ts, languageService, options) { console.warn(`Cannot find source file ${filename}`); return; } + // This source file should survive even if it is empty + markNeededSourceFile(sourceFile); enqueue_black(sourceFile); } function enqueueImport(node, importText) { @@ -472,6 +493,10 @@ function markNodes(ts, languageService, options) { const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node); if (symbolImportNode) { setColor(symbolImportNode, 2 /* NodeColor.Black */); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + } } if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { for (let i = 0, len = symbol.declarations.length; i < len; i++) { @@ -663,11 +688,23 @@ function generateResult(ts, languageService, shakeLevel) { } if (getColor(sourceFile) !== 2 /* NodeColor.Black */) { if (!nodeOrChildIsBlack(sourceFile)) { - // none of the elements are reachable => don't write this file at all! - return; + // none of the elements are reachable + if (isNeededSourceFile(sourceFile)) { + // this source file must be written, even if nothing is used from it + // because there is an import somewhere for it. + // However, TS complains with empty files with the error "x" is not a module, + // so we will export a dummy variable + result = 'export const __dummy = 0;'; + } + else { + // don't write this file at all! + return; + } + } + else { + sourceFile.forEachChild(writeMarkedNodes); + result += sourceFile.endOfFileToken.getFullText(sourceFile); } - sourceFile.forEachChild(writeMarkedNodes); - result += sourceFile.endOfFileToken.getFullText(sourceFile); } else { result = text; diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index eb4f6f7767a..ef829426d94 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -307,6 +307,12 @@ function getColor(node: ts.Node): NodeColor { function setColor(node: ts.Node, color: NodeColor): void { (node).$$$color = color; } +function markNeededSourceFile(node: ts.SourceFile): void { + (node).$$$neededSourceFile = true; +} +function isNeededSourceFile(node: ts.SourceFile): boolean { + return Boolean((node).$$$neededSourceFile); +} function nodeOrParentIsBlack(node: ts.Node): boolean { while (node) { const color = getColor(node); @@ -449,6 +455,20 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language }); } + /** + * Return the parent of `node` which is an ImportDeclaration + */ + function findParentImportDeclaration(node: ts.Declaration): ts.ImportDeclaration | null { + let _node: ts.Node = node; + do { + if (ts.isImportDeclaration(_node)) { + return _node; + } + _node = _node.parent; + } while (_node); + return null; + } + function enqueue_gray(node: ts.Node): void { if (nodeOrParentIsBlack(node) || getColor(node) === NodeColor.Gray) { return; @@ -531,6 +551,8 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language console.warn(`Cannot find source file ${filename}`); return; } + // This source file should survive even if it is empty + markNeededSourceFile(sourceFile); enqueue_black(sourceFile); } @@ -590,6 +612,10 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node); if (symbolImportNode) { setColor(symbolImportNode, NodeColor.Black); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + } } if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { @@ -802,11 +828,21 @@ function generateResult(ts: typeof import('typescript'), languageService: ts.Lan if (getColor(sourceFile) !== NodeColor.Black) { if (!nodeOrChildIsBlack(sourceFile)) { - // none of the elements are reachable => don't write this file at all! - return; + // none of the elements are reachable + if (isNeededSourceFile(sourceFile)) { + // this source file must be written, even if nothing is used from it + // because there is an import somewhere for it. + // However, TS complains with empty files with the error "x" is not a module, + // so we will export a dummy variable + result = 'export const __dummy = 0;'; + } else { + // don't write this file at all! + return; + } + } else { + sourceFile.forEachChild(writeMarkedNodes); + result += sourceFile.endOfFileToken.getFullText(sourceFile); } - sourceFile.forEachChild(writeMarkedNodes); - result += sourceFile.endOfFileToken.getFullText(sourceFile); } else { result = text; } diff --git a/cglicenses.json b/cglicenses.json index 4ce1e015e46..5612be62696 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -173,5 +173,32 @@ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", "SOFTWARE" ] + }, + { + // Reason: Waiting for https://github.com/microsoft/vscode-markdown-languageservice/pull/9 + "name": "vscode-markdown-languageservice", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) Microsoft Corporation.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE" + ] } ] diff --git a/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json b/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json new file mode 100644 index 00000000000..d224f919109 --- /dev/null +++ b/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json @@ -0,0 +1,189 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "customizations": { + "type": "object", + "properties": { + "codespaces": { + "type": "object", + "description": "Customizations specific to GitHub Codespaces", + "properties": { + "repositories": { + "type": "object", + "description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')", + "patternProperties": { + "^[a-zA-Z0-9-_.]+[.]*\/[a-zA-Z0-9-_*]+[.]*$": { + "type": "object", + "additionalProperties": true, + "oneOf": [ + { + "properties": { + "permissions": { + "type": "object", + "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", + "additionalProperties": true, + "anyOf": [ + { + "properties": { + "actions": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "checks": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "contents": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "deployments": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "discussions": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "issues": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "packages": { + "type": "string", + "enum": [ + "read" + ] + } + } + }, + { + "properties": { + "pages": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "pull_requests": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "repository_projects": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "statuses": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "workflows": { + "type": "string", + "enum": [ + "write" + ] + } + } + } + ] + } + } + }, + { + "properties": { + "permissions": { + "type": "string", + "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", + "enum": [ + "read-all", + "write-all" + ] + } + } + } + ] + } + } + } + } + } + } + }, + "codespaces": { + "type": "object", + "additionalProperties": true, + "description": "Codespaces-specific configuration.", + "deprecated": true, + "deprecationMessage": "Use 'customizations/codespaces' instead" + } + } +} diff --git a/extensions/configuration-editing/schemas/devContainer.vscode.schema.json b/extensions/configuration-editing/schemas/devContainer.vscode.schema.json new file mode 100644 index 00000000000..e0f2a8aa68f --- /dev/null +++ b/extensions/configuration-editing/schemas/devContainer.vscode.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "customizations": { + "type": "object", + "properties": { + "vscode": { + "type": "object", + "properties": { + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + } + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again." + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend." + } + } + } + } + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)((@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|@prerelease)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + }, + "deprecated": true, + "deprecationMessage": "Use 'customizations/vscode/extensions' instead" + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again.", + "deprecated": true, + "deprecationMessage": "Use 'customizations/vscode/settings' instead" + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend.", + "deprecated": true, + "deprecationMessage": "Use 'customizations/vscode/devPort' instead" + } + } +} diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index d9deee50fb3..9cbe3bf74a6 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -65,6 +65,7 @@ "emmet.showAbbreviationSuggestions": { "type": "boolean", "default": true, + "scope": "language-overridable", "markdownDescription": "%emmetShowAbbreviationSuggestions%" }, "emmet.includeLanguages": { @@ -125,6 +126,7 @@ "emmet.triggerExpansionOnTab": { "type": "boolean", "default": false, + "scope": "language-overridable", "markdownDescription": "%emmetTriggerExpansionOnTab%" }, "emmet.useInlineCompletions": { diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 527b5b1476c..a0d97dfadb8 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -95,18 +95,19 @@ export class ActionButtonCommand { return { command: this.getCommitActionButtonPrimaryCommand(), secondaryCommands: this.getCommitActionButtonSecondaryCommands(), - enabled: this.state.repositoryHasChangesToCommit && !this.state.isCommitInProgress && !this.state.isMergeInProgress + enabled: (this.state.repositoryHasChangesToCommit || this.state.isRebaseInProgress) && !this.state.isCommitInProgress && !this.state.isMergeInProgress }; } private getCommitActionButtonPrimaryCommand(): Command { - let commandArg = ''; - let title = localize('scm button commit title', "{0} Commit", '$(check)'); - let tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes"); - // Rebase Continue if (this.state.isRebaseInProgress) { - return { command: 'git.commit', title, tooltip, arguments: [this.repository.sourceControl, commandArg] }; + return { + command: 'git.commit', + title: localize('scm button continue title', "{0} Continue", '$(check)'), + tooltip: this.state.isCommitInProgress ? localize('scm button continuing tooltip', "Continuing Rebase...") : localize('scm button continue tooltip', "Continue Rebase"), + arguments: [this.repository.sourceControl, ''] + }; } // Commit @@ -122,6 +123,10 @@ export class ActionButtonCommand { // Icon const icon = alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined; + let commandArg = ''; + let title = localize('scm button commit title', "{0} Commit", icon ?? '$(check)'); + let tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes"); + // Title, tooltip switch (postCommitCommand) { case 'push': { diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 560734f6316..66032dc217c 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1558,6 +1558,8 @@ export class CommandCenter { // amend allows changing only the commit message && !opts.amend && !opts.empty + // rebase not in progress + && repository.rebaseCommit === undefined ) { const commitAnyway = localize('commit anyway', "Create Empty Commit"); const answer = await window.showInformationMessage(localize('no changes', "There are no changes to commit."), commitAnyway); @@ -1770,7 +1772,7 @@ export class CommandCenter { const shouldPrompt = config.get('confirmEmptyCommits') === true; if (shouldPrompt) { - const message = localize('confirm emtpy commit', "Are you sure you want to create an empty commit?"); + const message = localize('confirm empty commit', "Are you sure you want to create an empty commit?"); const yes = localize('yes', "Yes"); const neverAgain = localize('yes never again', "Yes, Don't Show Again"); const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index 40a6927146d..af3484580fd 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -9,9 +9,13 @@ import { publishRepository } from './publish'; import { DisposableStore } from './util'; import { getPermalink } from './links'; +function getVscodeDevHost(): string { + return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`; +} + async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) { try { - const permalink = getPermalink(gitAPI, useSelection, 'https://vscode.dev/github'); + const permalink = getPermalink(gitAPI, useSelection, getVscodeDevHost()); if (permalink) { return vscode.env.clipboard.writeText(permalink); } @@ -22,7 +26,7 @@ async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) { async function openVscodeDevLink(gitAPI: GitAPI): Promise { try { - const permalink = getPermalink(gitAPI, true, 'https://vscode.dev/github'); + const permalink = getPermalink(gitAPI, true, getVscodeDevHost()); return permalink ? vscode.Uri.parse(permalink) : undefined; } catch (err) { vscode.window.showErrorMessage(err.message); diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts index 22ede57ccbd..66f5b071ecd 100644 --- a/extensions/github/src/links.ts +++ b/extensions/github/src/links.ts @@ -125,11 +125,11 @@ export function getPermalink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: return; } - const commitHash = gitRepo.state.HEAD?.commit; + const commitHash = (gitRepo.state.HEAD?.ahead === 0) ? `/blob/${gitRepo.state.HEAD?.commit}` : ''; const fileSegments = fileAndPosition.type === LinkType.File ? (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${rangeString(fileAndPosition.range)}` : '') : (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range)}` : ''); - return `${hostPrefix}/${repo.owner}/${repo.repo}/blob/${commitHash + return `${hostPrefix}/${repo.owner}/${repo.repo}${commitHash }${fileSegments}`; } diff --git a/extensions/ipynb/.gitignore b/extensions/ipynb/.gitignore index 170d890802b..8c0ca40ca20 100644 --- a/extensions/ipynb/.gitignore +++ b/extensions/ipynb/.gitignore @@ -2,3 +2,4 @@ out dist node_modules *.vsix +notebook-out diff --git a/extensions/ipynb/.vscodeignore b/extensions/ipynb/.vscodeignore index 08245c01482..f45314d0c1e 100644 --- a/extensions/ipynb/.vscodeignore +++ b/extensions/ipynb/.vscodeignore @@ -6,4 +6,4 @@ extension.webpack.config.js extension-browser.webpack.config.js yarn.lock .gitignore - +esbuild.js diff --git a/extensions/ipynb/esbuild.js b/extensions/ipynb/esbuild.js index 444ec268030..973d26faf26 100644 --- a/extensions/ipynb/esbuild.js +++ b/extensions/ipynb/esbuild.js @@ -19,7 +19,7 @@ if (outputRootIndex >= 0) { } const srcDir = path.join(__dirname, 'src'); -const outDir = path.join(outputRoot, 'out'); +const outDir = path.join(outputRoot, 'notebook-out'); async function build() { await esbuild.build({ diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 7e057f605b7..b7250aee1f4 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -57,7 +57,7 @@ "displayName": "Markdown it ipynb Cell Attachment renderer", "entrypoint": { "extends": "vscode.markdown-it-renderer", - "path": "./out/cellAttachmentRenderer.js" + "path": "./notebook-out/cellAttachmentRenderer.js" } } ], diff --git a/extensions/ipynb/src/cellAttachmentRenderer.ts b/extensions/ipynb/src/cellAttachmentRenderer.ts index 7e05834dbcb..69e294d57b9 100644 --- a/extensions/ipynb/src/cellAttachmentRenderer.ts +++ b/extensions/ipynb/src/cellAttachmentRenderer.ts @@ -22,11 +22,19 @@ export async function activate(ctx: RendererContext) { md.renderer.rules.image = (tokens: MarkdownItToken[], idx: number, options, env, self) => { const token = tokens[idx]; const src = token.attrGet('src'); - const attachments: Record> = env.outputItem.metadata?.custom?.attachments; + const attachments: Record> = env.outputItem.metadata?.custom?.attachments; // this stores attachment entries for every image in the cell if (attachments && src) { - const [attachmentKey, attachmentVal] = Object.entries(attachments[src.replace('attachment:', '')])[0]; - const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal; - token.attrSet('src', b64Markdown); + const imageAttachment = attachments[src.replace('attachment:', '')]; + if (imageAttachment) { + // objEntries will always be length 1, with objEntries[0] holding [0]=mime,[1]=b64 + // if length = 0, something is wrong with the attachment, mime/b64 weren't copied over + const objEntries = Object.entries(imageAttachment); + if (objEntries.length) { + const [attachmentKey, attachmentVal] = objEntries[0]; + const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal; + token.attrSet('src', b64Markdown); + } + } } if (original) { diff --git a/extensions/markdown-language-features/.vscodeignore b/extensions/markdown-language-features/.vscodeignore index 1046b7ec26b..fa12077f015 100644 --- a/extensions/markdown-language-features/.vscodeignore +++ b/extensions/markdown-language-features/.vscodeignore @@ -12,7 +12,8 @@ cgmanifest.json yarn.lock preview-src/** webpack.config.js -esbuild.js +esbuild-notebook.js +esbuild-preview.js .gitignore server/src/** server/extension.webpack.config.js diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index a036374fed1..70dcfd6984b 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -8,57 +8,108 @@ import MarkdownIt from 'markdown-it'; import type * as MarkdownItToken from 'markdown-it/lib/token'; import type { ActivationFunction } from 'vscode-notebook-renderer'; +const allowedHtmlTags = Object.freeze([ + 'a', + 'b', + 'blockquote', + 'br', + 'button', + 'caption', + 'center', + 'code', + 'col', + 'colgroup', + 'details', + 'div', + 'em', + 'font', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'hr', + 'i', + 'img', + 'input', + 'kbd', + 'label', + 'li', + 'ol', + 'p', + 'pre', + 'select', + 'small', + 'span', + 'strong', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'tr', + 'tt', + 'u', + 'ul', + 'video', +]); + +const allowedSvgTags = Object.freeze([ + 'svg', + 'a', + 'altglyph', + 'altglyphdef', + 'altglyphitem', + 'animatecolor', + 'animatemotion', + 'animatetransform', + 'circle', + 'clippath', + 'defs', + 'desc', + 'ellipse', + 'filter', + 'font', + 'g', + 'glyph', + 'glyphref', + 'hkern', + 'image', + 'line', + 'lineargradient', + 'marker', + 'mask', + 'metadata', + 'mpath', + 'path', + 'pattern', + 'polygon', + 'polyline', + 'radialgradient', + 'rect', + 'stop', + 'style', + 'switch', + 'symbol', + 'text', + 'textpath', + 'title', + 'tref', + 'tspan', + 'view', + 'vkern', +]); + const sanitizerOptions: DOMPurify.Config = { ALLOWED_TAGS: [ - 'a', - 'b', - 'blockquote', - 'br', - 'button', - 'caption', - 'center', - 'code', - 'col', - 'colgroup', - 'details', - 'div', - 'em', - 'font', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'hr', - 'i', - 'img', - 'input', - 'kbd', - 'label', - 'li', - 'ol', - 'p', - 'pre', - 'select', - 'small', - 'span', - 'strong', - 'sub', - 'summary', - 'sup', - 'table', - 'tbody', - 'td', - 'textarea', - 'tfoot', - 'th', - 'thead', - 'tr', - 'tt', - 'u', - 'ul', - 'video', + ...allowedHtmlTags, + ...allowedSvgTags, ], }; diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 062c6ac899f..1c32e447e05 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -29,7 +29,7 @@ "configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.", "configuration.markdown.links.openLocation.beside": "Open links beside the active editor.", "configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links", - "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbench.editor.dropIntoEditor.enabled#`.", + "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#editor.dropIntoEditor.enabled#`.", "configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links. Requires enabling `#editor.experimental.pasteActions.enabled#`.", "configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.", "configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index bb22142e584..4c3b9f2a38e 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -11,7 +11,7 @@ import * as nls from 'vscode-nls'; import { v4 as uuid } from 'uuid'; import fetch, { Response } from 'node-fetch'; import Logger from './logger'; -import { toBase64UrlEncoding } from './utils'; +import { isSupportedEnvironment, toBase64UrlEncoding } from './utils'; import { sha256 } from './env/node/sha256'; import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage'; import { LoopbackAuthServer } from './authServer'; @@ -319,13 +319,7 @@ export class AzureActiveDirectoryService { }, 5000); } - const token = await this.exchangeCodeForToken(codeToExchange, codeVerifier, scopeData); - if (token.expiresIn) { - this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); - } - await this.setToken(token, scopeData); - Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); - const session = await this.convertToSession(token); + const session = await this.exchangeCodeForSession(codeToExchange, codeVerifier, scopeData); return session; } @@ -355,9 +349,11 @@ export class AzureActiveDirectoryService { const uri = vscode.Uri.parse(`${signInUrl}?${oauthStartQuery.toString()}`); vscode.env.openExternal(uri); + let inputBox: vscode.InputBox | undefined; const timeoutPromise = new Promise((_: (value: vscode.AuthenticationSession) => void, reject) => { const wait = setTimeout(() => { clearTimeout(wait); + inputBox?.dispose(); reject('Login timed out.'); }, 1000 * 60 * 5); }); @@ -369,7 +365,12 @@ export class AzureActiveDirectoryService { // before completing it. let existingPromise = this._codeExchangePromises.get(scopeData.scopeStr); if (!existingPromise) { - existingPromise = this.handleCodeResponse(scopeData); + if (isSupportedEnvironment(callbackUri)) { + existingPromise = this.handleCodeResponse(scopeData); + } else { + inputBox = vscode.window.createInputBox(); + existingPromise = Promise.race([this.handleCodeInputBox(inputBox, codeVerifier, scopeData), this.handleCodeResponse(scopeData)]); + } this._codeExchangePromises.set(scopeData.scopeStr, existingPromise); } @@ -659,13 +660,7 @@ export class AzureActiveDirectoryService { throw new Error('No available code verifier'); } - const token = await this.exchangeCodeForToken(code, verifier, scopeData); - if (token.expiresIn) { - this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); - } - await this.setToken(token, scopeData); - - const session = await this.convertToSession(token); + const session = await this.exchangeCodeForSession(code, verifier, scopeData); resolve(session); } catch (err) { reject(err); @@ -680,8 +675,33 @@ export class AzureActiveDirectoryService { }); } - private async exchangeCodeForToken(code: string, codeVerifier: string, scopeData: IScopeData): Promise { + private async handleCodeInputBox(inputBox: vscode.InputBox, verifier: string, scopeData: IScopeData): Promise { + inputBox.ignoreFocusOut = true; + inputBox.title = localize('pasteCodeTitle', 'Microsoft Authentication'); + inputBox.prompt = localize('pasteCodePrompt', 'Provide the authorization code to complete the sign in flow.'); + inputBox.placeholder = localize('pasteCodePlaceholder', 'Paste authorization code here...'); + return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => { + inputBox.show(); + inputBox.onDidAccept(async () => { + const code = inputBox.value; + if (code) { + inputBox.dispose(); + const session = await this.exchangeCodeForSession(code, verifier, scopeData); + resolve(session); + } + }); + inputBox.onDidHide(() => { + if (!inputBox.value) { + inputBox.dispose(); + reject('Cancelled'); + } + }); + }); + } + + private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise { Logger.info(`Exchanging login code for token for scopes: ${scopeData.scopeStr}`); + let token: IToken | undefined; try { const postData = querystring.stringify({ grant_type: 'authorization_code', @@ -698,11 +718,18 @@ export class AzureActiveDirectoryService { const json = await this.fetchTokenResponse(endpoint, postData, scopeData); Logger.info(`Exchanging login code for token (for scopes: ${scopeData.scopeStr}) succeeded!`); - return this.convertToTokenSync(json, scopeData); + token = this.convertToTokenSync(json, scopeData); } catch (e) { Logger.error(`Error exchanging code for token (for scopes ${scopeData.scopeStr}): ${e}`); throw e; } + + if (token.expiresIn) { + this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); + } + await this.setToken(token, scopeData); + Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); + return await this.convertToSession(token); } private async fetchTokenResponse(endpoint: string, postData: string, scopeData: IScopeData): Promise { diff --git a/extensions/microsoft-authentication/src/utils.ts b/extensions/microsoft-authentication/src/utils.ts index 164f2236221..443bb2dc048 100644 --- a/extensions/microsoft-authentication/src/utils.ts +++ b/extensions/microsoft-authentication/src/utils.ts @@ -2,7 +2,40 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { env, UIKind, Uri } from 'vscode'; export function toBase64UrlEncoding(base64string: string) { return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding } + +const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; +function isLocalhost(uri: Uri): boolean { + if (!/^https?$/i.test(uri.scheme)) { + return false; + } + const host = uri.authority.split(':')[0]; + return LOCALHOST_ADDRESSES.indexOf(host) >= 0; +} + +export function isSupportedEnvironment(uri: Uri): boolean { + if (env.uiKind === UIKind.Desktop) { + return true; + } + // local development (localhost:* or 127.0.0.1:*) + if (isLocalhost(uri)) { + return true; + } + // At this point we should only ever see https + if (uri.scheme !== 'https') { + return false; + } + + return ( + // vscode.dev & insiders.vscode.dev + /(?:^|\.)vscode\.dev$/.test(uri.authority) || + // github.dev & codespaces + /(?:^|\.)github\.dev$/.test(uri.authority) || + // github.dev/codespaces local setup (github.localhost) + /(?:^|\.)github\.localhost$/.test(uri.authority) + ); +} diff --git a/extensions/simple-browser/.vscodeignore b/extensions/simple-browser/.vscodeignore index ec298ce1768..d1006b25b79 100644 --- a/extensions/simple-browser/.vscodeignore +++ b/extensions/simple-browser/.vscodeignore @@ -11,3 +11,4 @@ cgmanifest.json yarn.lock preview-src/** webpack.config.js +esbuild-preview.js diff --git a/extensions/theme-seti/build/update-icon-theme.js b/extensions/theme-seti/build/update-icon-theme.js index 66e11c16c5e..c55af7ab7fe 100644 --- a/extensions/theme-seti/build/update-icon-theme.js +++ b/extensions/theme-seti/build/update-icon-theme.js @@ -45,7 +45,8 @@ const nonBuiltInLanguages = { // { fileNames, extensions } const inheritIconFromLanguage = { "jsonc": 'json', "postcss": 'css', - "django-html": 'html' + "django-html": 'html', + "blade": 'php' } const FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index 983ec9c5157..919b27b7c91 100644 --- a/extensions/theme-seti/cgmanifest.json +++ b/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "4dd6c27e1f5aed8068c2451dbaf0db3364545937" + "commitHash": "2d10473b7575ec00c47eda751ea9caeec6b0b606" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index 24cde729664..f0e47486995 100644 Binary files a/extensions/theme-seti/icons/seti.woff and b/extensions/theme-seti/icons/seti.woff differ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 0914afe46c1..abf5c8a0a7e 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -1694,6 +1694,7 @@ "tf": "_terraform", "tf.json": "_terraform", "tfvars": "_terraform", + "tfvars.json": "_terraform", "dtx": "_tex_2", "ins": "_tex_3", "toml": "_config", @@ -1942,7 +1943,8 @@ "vala": "_vala", "vue": "_vue", "postcss": "_css", - "django-html": "_html_3" + "django-html": "_html_3", + "blade": "_php" }, "light": { "file": "_default_light", @@ -2093,6 +2095,7 @@ "tf": "_terraform_light", "tf.json": "_terraform_light", "tfvars": "_terraform_light", + "tfvars.json": "_terraform_light", "dtx": "_tex_2_light", "ins": "_tex_3_light", "toml": "_config_light", @@ -2258,7 +2261,8 @@ "vala": "_vala_light", "vue": "_vue_light", "postcss": "_css_light", - "django-html": "_html_3_light" + "django-html": "_html_3_light", + "blade": "_php_light" }, "fileNames": { "mix": "_hex_light", @@ -2340,5 +2344,5 @@ "npm-debug.log": "_npm_ignored_light" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/4dd6c27e1f5aed8068c2451dbaf0db3364545937" + "version": "https://github.com/jesseweed/seti-ui/commit/2d10473b7575ec00c47eda751ea9caeec6b0b606" } \ No newline at end of file diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index b09593f6c59..0c740259823 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -430,32 +430,29 @@ export default class TypeScriptServiceClient extends Disposable implements IType }); handle.onExit((data: TypeScriptServerExitEvent) => { + const { code, signal } = data; + this.error(`TSServer exited. Code: ${code}. Signal: ${signal}`); + + // In practice, the exit code is an integer with no ties to any identity, + // so it can be classified as SystemMetaData, rather than CallstackOrException. + /* __GDPR__ + "tsserver.exitWithCode" : { + "owner": "mjbvz", + "code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "${include}": [ + "${TypeScriptCommonProperties}" + ] + } + */ + this.logTelemetry('tsserver.exitWithCode', { code: code ?? undefined, signal: signal ?? undefined }); + + if (this.token !== mytoken) { // this is coming from an old process return; } - const { code, signal } = data; - - if (code === null || typeof code === 'undefined') { - this.info(`TSServer exited. Signal: ${signal}`); - } else { - // In practice, the exit code is an integer with no ties to any identity, - // so it can be classified as SystemMetaData, rather than CallstackOrException. - this.error(`TSServer exited with code: ${code}. Signal: ${signal}`); - /* __GDPR__ - "tsserver.exitWithCode" : { - "owner": "mjbvz", - "code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "${include}": [ - "${TypeScriptCommonProperties}" - ] - } - */ - this.logTelemetry('tsserver.exitWithCode', { code, signal: signal ?? undefined }); - } - if (handle.tsServerLogFile) { this.info(`TSServer log file: ${handle.tsServerLogFile}`); } diff --git a/package.json b/package.json index f15864fabdc..0e570c2c0ae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.70.0", - "distro": "990065ff739688c0d4ad94e172eeccd6bd5ef124", + "distro": "25c5a766dec5d762c3205176fec650c6964c7763", "author": { "name": "Microsoft Corporation" }, diff --git a/product.json b/product.json index a50c00f6cf3..0e0d703e181 100644 --- a/product.json +++ b/product.json @@ -46,7 +46,7 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.69.0", + "version": "1.70.0", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", @@ -61,7 +61,7 @@ }, { "name": "ms-vscode.vscode-js-profile-table", - "version": "1.0.2", + "version": "1.0.3", "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", "metadata": { "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", diff --git a/resources/linux/snap/snapcraft.yaml b/resources/linux/snap/snapcraft.yaml index 8bbad497ace..fc775b6554f 100644 --- a/resources/linux/snap/snapcraft.yaml +++ b/resources/linux/snap/snapcraft.yaml @@ -58,11 +58,9 @@ apps: command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --no-sandbox common-id: @@NAME@@.desktop environment: - DISABLE_WAYLAND: 1 GSETTINGS_SCHEMA_DIR: $SNAP/usr/share/glib-2.0/schemas url-handler: command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --open-url --no-sandbox environment: - DISABLE_WAYLAND: 1 GSETTINGS_SCHEMA_DIR: $SNAP/usr/share/glib-2.0/schemas diff --git a/src/tsec.exemptions.json b/src/tsec.exemptions.json index 0bc41dc4d7e..7ae1cd574e0 100644 --- a/src/tsec.exemptions.json +++ b/src/tsec.exemptions.json @@ -15,6 +15,7 @@ "vs/base/browser/defaultWorkerFactory.ts", "vs/base/worker/workerMain.ts", "vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts", + "vs/editor/contrib/stickyScroll/browser/stickyScroll.ts", "vs/editor/browser/view/domLineBreaksComputer.ts", "vs/editor/browser/view/viewLayer.ts", "vs/editor/browser/widget/diffEditorWidget.ts", diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 4019ea90b61..06d13c90d93 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1869,9 +1869,11 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia typeof cssValue === 'number' ? cssValue + 'px' : '' + cssValue ); } - continue; + } else if (key === 'tabIndex') { + el.tabIndex = value; + } else { + el.setAttribute(camelCaseToHyphenCase(key), value.toString()); } - el.setAttribute(camelCaseToHyphenCase(key), value.toString()); } result['root'] = el; diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 0af76e3b6a0..00b21774ea0 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -126,9 +126,22 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { }; } + this.updateTooltip(); this.updateEnabled(); } + override getTooltip(): string | undefined { + let title: string | null = null; + + if (this.getAction().tooltip) { + title = this.getAction().tooltip; + } else if (this.getAction().label) { + title = this.getAction().label; + } + + return title ?? undefined; + } + override setActionContext(newContext: unknown): void { super.setActionContext(newContext); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 7a75aa8070a..1032f3e48a9 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -593,11 +593,11 @@ class FindFilter implements ITreeFilter, IDi } filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult { + let visibility = TreeVisibility.Visible; + if (this._filter) { const result = this._filter.filter(element, parentVisibility); - let visibility: TreeVisibility; - if (typeof result === 'boolean') { visibility = result ? TreeVisibility.Visible : TreeVisibility.Hidden; } else if (isFilterResult(result)) { @@ -615,7 +615,7 @@ class FindFilter implements ITreeFilter, IDi if (!this._pattern) { this._matchCount++; - return { data: FuzzyScore.Default, visibility: true }; + return { data: FuzzyScore.Default, visibility }; } const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(element); @@ -624,22 +624,22 @@ class FindFilter implements ITreeFilter, IDi for (const l of labels) { const labelStr = l && l.toString(); if (typeof labelStr === 'undefined') { - return { data: FuzzyScore.Default, visibility: true }; + return { data: FuzzyScore.Default, visibility }; } const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true }); if (score) { this._matchCount++; return labels.length === 1 ? - { data: score, visibility: true } : - { data: { label: labelStr, score: score }, visibility: true }; + { data: score, visibility } : + { data: { label: labelStr, score: score }, visibility }; } } if (this.tree.findMode === TreeFindMode.Filter) { return TreeVisibility.Recurse; } else { - return { data: FuzzyScore.Default, visibility: true }; + return { data: FuzzyScore.Default, visibility }; } } @@ -685,7 +685,7 @@ export enum TreeFindMode { class FindWidget extends Disposable { private readonly elements = h('.monaco-tree-type-filter', [ - h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab'), + h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab', { tabIndex: 0 }), h('.monaco-tree-type-filter-input@findInput'), h('.monaco-tree-type-filter-actionbar@actionbar'), ]); @@ -699,7 +699,7 @@ class FindWidget extends Disposable { private readonly findInput: FindInput; private readonly actionbar: ActionBar; private width = 0; - private right = 4; + private right = 0; readonly _onDidDisable = new Emitter(); readonly onDidDisable = this._onDidDisable.event; @@ -772,6 +772,28 @@ class FindWidget extends Disposable { })); })); + const onGrabKeyDown = this._register(Event.chain(this._register(new DomEmitter(this.elements.grab, 'keydown')).event)) + .map(e => new StandardKeyboardEvent(e)) + .event; + + this._register(onGrabKeyDown((e): any => { + let right: number | undefined; + + if (e.keyCode === KeyCode.LeftArrow) { + right = Number.POSITIVE_INFINITY; + } else if (e.keyCode === KeyCode.RightArrow) { + right = 0; + } else if (e.keyCode === KeyCode.Space) { + right = this.right === 0 ? Number.POSITIVE_INFINITY : 0; + } + + if (right !== undefined) { + e.preventDefault(); + e.stopPropagation(); + this.right = right; + this.layout(); + } + })); this.onDidChangeValue = this.findInput.onDidChange; this.style(options ?? {}); @@ -799,7 +821,7 @@ class FindWidget extends Disposable { layout(width: number = this.width): void { this.width = width; - this.right = Math.min(Math.max(20, this.right), Math.max(20, width - 170)); + this.right = clamp(this.right, 0, Math.max(0, width - 212)); this.elements.root.style.right = `${this.right}px`; } @@ -876,7 +898,8 @@ class FindController implements IDisposable { return; } - this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this._mode, this.styles); + this.mode = this.tree.options.defaultFindMode ?? TreeFindMode.Highlight; + this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this.mode, this.styles); this.enabledDisposables.add(this.widget); this.widget.onDidChangeValue(this.onDidChangeValue, this, this.enabledDisposables); @@ -1022,6 +1045,7 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly filter?: ITreeFilter; readonly dnd?: ITreeDragAndDrop; readonly additionalScrollHeight?: number; + readonly findWidgetEnabled?: boolean; } function dfs(node: ITreeNode, fn: (node: ITreeNode) => void): void { @@ -1436,7 +1460,7 @@ export abstract class AbstractTree implements IDisposable onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables); } - if (_options.keyboardNavigationLabelProvider && _options.contextViewProvider) { + if ((_options.findWidgetEnabled ?? true) && _options.keyboardNavigationLabelProvider && _options.contextViewProvider) { this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider); this.focusNavigationFilter = node => this.findController!.shouldAllowFocus(node); this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState; diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index f6dceb39e95..1a2c0492ea7 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -74,8 +74,9 @@ display: flex; padding: 3px; transition: top 0.3s; - width: 160px; + max-width: 200px; z-index: 100; + margin: 0 6px; } .monaco-tree-type-filter.disabled { @@ -87,6 +88,7 @@ align-items: center; justify-content: center; cursor: grab; + margin-right: 2px; } .monaco-tree-type-filter-grab.grabbing { @@ -98,12 +100,16 @@ } .monaco-tree-type-filter-input .monaco-inputbox { - height: 26px; + height: 23px; } .monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .input, .monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .mirror { - padding: 2px; + padding: 2px 4px; +} + +.monaco-tree-type-filter-input .monaco-findInput > .controls { + top: 2px; } .monaco-tree-type-filter-actionbar { diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index d395e4a9f32..5ceab5b92a0 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -8,7 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; -import { IObservable, IObserver } from 'vs/base/common/observableImpl/base'; +import { IObservable, IObserver } from 'vs/base/common/observable'; import { StopWatch } from 'vs/base/common/stopwatch'; diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 63d6a15b49e..20f6b38e629 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -101,6 +101,11 @@ export namespace Schemas { * Scheme used vs live share */ export const vsls = 'vsls'; + + /** + * Scheme used for the Source Control commit input's text document + */ + export const vscodeSourceControl = 'vscode-scm'; } export const connectionTokenCookieName = 'vscode-tkn'; diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index e3844cd39e8..3e1cb2a7354 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -275,9 +275,3 @@ export type UriDto = { [K in keyof T]: T[K] extends URI export function assertNever(value: never, message = 'Unreachable'): never { throw new Error(message); } - -/** - * Given an object with all optional properties, requires at least one to be defined. - * i.e. AtLeastOne; - */ -export type AtLeastOne }> = Partial & U[keyof U]; diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index efab21e77bf..e3357d75fe8 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -7,7 +7,6 @@ position: absolute; width: 600px; z-index: 2550; - padding: 0 1px 1px 1px; left: 50%; margin-left: -300px; -webkit-app-region: no-drag; @@ -151,6 +150,7 @@ .quick-input-list { line-height: 22px; margin-top: 6px; + padding: 0px 1px 1px 1px; } .quick-input-widget.hidden-input .quick-input-list { diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 7f2a4965b81..d959a938e93 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -62,7 +62,7 @@ import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/ import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils'; +import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; import { LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; @@ -277,19 +277,20 @@ class SharedProcessMain extends Disposable { // Telemetry let telemetryService: ITelemetryService; const appenders: ITelemetryAppender[] = []; + const internalTelemetry = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { const logAppender = new TelemetryLogAppender(loggerService, environmentService); appenders.push(logAppender); const { installSourcePath } = environmentService; if (productService.aiConfig?.ariaKey) { - const collectorAppender = new OneDataSystemWebAppender(configurationService, 'monacoworkbench', null, productService.aiConfig.ariaKey); + const collectorAppender = new OneDataSystemWebAppender(internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey); this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data appenders.push(collectorAppender); } telemetryService = new TelemetryService({ appenders, - commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, productService.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, internalTelemetry, installSourcePath), sendErrorTelemetry: true, piiPaths: getPiiPathsFromEnvironment(environmentService), }, configurationService, productService); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 670685ffd9d..b13a6098ddd 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -75,7 +75,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProp import { ITelemetryService, machineIdKey, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { getPiiPathsFromEnvironment, getTelemetryLevel, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { getPiiPathsFromEnvironment, getTelemetryLevel, isInternalTelemetry, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateChannel } from 'vs/platform/update/common/updateIpc'; import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; @@ -680,9 +680,10 @@ export class CodeApplication extends Disposable { // Telemetry if (supportsTelemetry(this.productService, this.environmentMainService)) { + const isInternal = isInternalTelemetry(this.productService, this.configurationService); const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); - const commonProperties = resolveCommonProperties(this.fileService, release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, this.productService.msftInternalDomains, this.environmentMainService.installSourcePath); + const commonProperties = resolveCommonProperties(this.fileService, release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, isInternal, this.environmentMainService.installSourcePath); const piiPaths = getPiiPathsFromEnvironment(this.environmentMainService); const config: ITelemetryServiceConfig = { appenders: [appender], commonProperties, piiPaths, sendErrorTelemetry: true }; diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index 47066f520be..16e089df650 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -4,7 +4,7 @@ - + diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index c64e6fe0503..3a006e635a3 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -63,10 +63,6 @@ export async function main(argv: string[]): Promise { // Shell integration else if (args['locate-shell-integration-path']) { - // Silently fail when the terminal is not VS Code's integrated terminal - if (process.env['TERM_PROGRAM'] !== 'vscode') { - return; - } let file: string; switch (args['locate-shell-integration-path']) { // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path bash)"` diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 79c5b02e686..eda61ad1689 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -52,7 +52,7 @@ import { StateService } from 'vs/platform/state/node/stateService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils'; +import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -187,9 +187,10 @@ class CliMain extends Disposable { // Telemetry const appenders: OneDataSystemAppender[] = []; + const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { if (productService.aiConfig && productService.aiConfig.ariaKey) { - appenders.push(new OneDataSystemAppender(configurationService, 'monacoworkbench', null, productService.aiConfig.ariaKey)); + appenders.push(new OneDataSystemAppender(isInternal, 'monacoworkbench', null, productService.aiConfig.ariaKey)); } const { installSourcePath } = environmentService; @@ -208,7 +209,7 @@ class CliMain extends Disposable { } } - return resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, machineId, productService.msftInternalDomains, installSourcePath); + return resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, machineId, isInternal, installSourcePath); })(), piiPaths: getPiiPathsFromEnvironment(environmentService) }; diff --git a/src/vs/editor/browser/coreCommands.ts b/src/vs/editor/browser/coreCommands.ts index ecc72a1a366..b5ab2a699e1 100644 --- a/src/vs/editor/browser/coreCommands.ts +++ b/src/vs/editor/browser/coreCommands.ts @@ -84,7 +84,7 @@ export namespace EditorScroll_ { \`\`\` * 'by': Unit to move. Default is computed based on 'to' value. \`\`\` - 'line', 'wrappedLine', 'page', 'halfPage' + 'line', 'wrappedLine', 'page', 'halfPage', 'editor' \`\`\` * 'value': Number of units to move. Default is '1'. * 'revealCursor': If 'true' reveals the cursor if it is outside view port. @@ -100,7 +100,7 @@ export namespace EditorScroll_ { }, 'by': { 'type': 'string', - 'enum': ['line', 'wrappedLine', 'page', 'halfPage'] + 'enum': ['line', 'wrappedLine', 'page', 'halfPage', 'editor'] }, 'value': { 'type': 'number', @@ -130,7 +130,8 @@ export namespace EditorScroll_ { Line: 'line', WrappedLine: 'wrappedLine', Page: 'page', - HalfPage: 'halfPage' + HalfPage: 'halfPage', + Editor: 'editor' }; /** @@ -172,6 +173,9 @@ export namespace EditorScroll_ { case RawUnit.HalfPage: unit = Unit.HalfPage; break; + case RawUnit.Editor: + unit = Unit.Editor; + break; default: unit = Unit.WrappedLine; } @@ -205,7 +209,8 @@ export namespace EditorScroll_ { Line = 1, WrappedLine = 2, Page = 3, - HalfPage = 4 + HalfPage = 4, + Editor = 5 } } @@ -1279,6 +1284,14 @@ export namespace CoreNavigationCommands { return viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); } + if (args.unit === EditorScroll_.Unit.Editor) { + let desiredTopModelLineNumber = 0; + if (args.direction === EditorScroll_.Direction.Down) { + desiredTopModelLineNumber = viewModel.model.getLineCount() - viewModel.cursorConfig.pageSize; + } + return viewModel.viewLayout.getVerticalOffsetForLineNumber(desiredTopModelLineNumber); + } + let noOfLines: number; if (args.unit === EditorScroll_.Unit.Page) { noOfLines = viewModel.cursorConfig.pageSize * args.value; @@ -1345,6 +1358,29 @@ export namespace CoreNavigationCommands { } }); + export const ScrollEditorTop: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'scrollEditorTop', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + } + }); + } + + runCoreEditorCommand(viewModel: IViewModel, args: any): void { + EditorScroll._runEditorScroll(viewModel, args.source, { + direction: EditorScroll_.Direction.Up, + unit: EditorScroll_.Unit.Editor, + value: 1, + revealCursor: false, + select: false + }); + } + }); + export const ScrollLineDown: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { constructor() { super({ @@ -1396,6 +1432,29 @@ export namespace CoreNavigationCommands { } }); + export const ScrollEditorBottom: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'scrollEditorBottom', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + } + }); + } + + runCoreEditorCommand(viewModel: IViewModel, args: any): void { + EditorScroll._runEditorScroll(viewModel, args.source, { + direction: EditorScroll_.Direction.Down, + unit: EditorScroll_.Unit.Editor, + value: 1, + revealCursor: false, + select: false + }); + } + }); + class WordCommand extends CoreEditorCommand { private readonly _inSelectionMode: boolean; diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index f91574dc4cb..25c2bbe8445 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -899,10 +899,15 @@ export interface ICodeEditor extends editorCommon.IEditor { getWhitespaces(): IEditorWhitespace[]; /** - * Get the vertical position (top offset) for the line w.r.t. to the first line. + * Get the vertical position (top offset) for the line's top w.r.t. to the first line. */ getTopForLineNumber(lineNumber: number): number; + /** + * Get the vertical position (top offset) for the line's bottom w.r.t. to the first line. + */ + getBottomForLineNumber(lineNumber: number): number; + /** * Get the vertical position (top offset) for the position w.r.t. to the first line. */ diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 0cc2cfa02af..32b66fa6680 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -368,10 +368,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._actions[internalAction.id] = internalAction; }); + const isDropIntoEnabled = () => { + return !this._configuration.options.get(EditorOption.readOnly) + && this._configuration.options.get(EditorOption.dropIntoEditor).enabled; + }; + this._register(new dom.DragAndDropObserver(this._domElement, { onDragEnter: () => undefined, onDragOver: e => { - if (!this._configuration.options.get(EditorOption.enableDropIntoEditor)) { + if (!isDropIntoEnabled()) { return; } @@ -381,7 +386,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } }, onDrop: async e => { - if (!this._configuration.options.get(EditorOption.enableDropIntoEditor)) { + if (!isDropIntoEnabled()) { return; } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index ee1a5a107b9..ef1aa278e53 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -1189,7 +1189,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE result.ariaLabel = options.originalAriaLabel; } result.readOnly = !this._options.originalEditable; - result.enableDropIntoEditor = !result.readOnly; + result.dropIntoEditor = { enabled: !result.readOnly }; result.extraEditorClassName = 'original-in-monaco-diff-editor'; return { ...result, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 31e39bf1cf4..15b9d5cddd8 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -169,6 +169,10 @@ export interface IEditorOptions { * Control the behavior and rendering of the scrollbars. */ scrollbar?: IEditorScrollbarOptions; + /** + * Control the behavior of experimental options + */ + experimental?: IEditorExperimentalOptions; /** * Control the behavior and rendering of the minimap. */ @@ -661,11 +665,11 @@ export interface IEditorOptions { bracketPairColorization?: IBracketPairColorizationOptions; /** - * Enables dropping into the editor from an external source. + * Controls dropping into the editor from an external source. * - * This shows a preview of the drop location and triggers an `onDropIntoEditor` event. + * When enabled, this shows a preview of the drop location and triggers an `onDropIntoEditor` event. */ - enableDropIntoEditor?: boolean; + dropIntoEditor?: IDropIntoEditorOptions; } /** @@ -876,7 +880,7 @@ function applyUpdate(value: T | undefined, update: T): ApplyUpdateResult { } if (Array.isArray(value) || Array.isArray(update)) { const arrayEquals = Array.isArray(value) && Array.isArray(update) && arrays.equals(value, update); - return new ApplyUpdateResult(update, arrayEquals); + return new ApplyUpdateResult(update, !arrayEquals); } let didChange = false; for (const key in update) { @@ -2504,6 +2508,57 @@ class EditorLightbulb extends BaseEditorOption { + + constructor() { + const defaults: EditorExperimentalOptions = { stickyScroll: { enabled: false } }; + super( + EditorOption.experimental, 'experimental', defaults, + { + 'editor.experimental.stickyScroll.enabled': { + type: 'boolean', + default: defaults.stickyScroll.enabled, + description: nls.localize('editor.experimental.stickyScroll', "Shows the nested current scopes during the scroll at the top of the editor.") + }, + } + ); + } + + public validate(_input: any): EditorExperimentalOptions { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IEditorExperimentalOptions; + return { + stickyScroll: { + enabled: boolean(input.stickyScroll?.enabled, this.defaultValue.stickyScroll.enabled) + } + }; + } +} + +//#endregion + //#region inlayHints /** @@ -4387,6 +4442,53 @@ class EditorWrappingInfoComputer extends ComputedEditorOption>; + +class EditorDropIntoEditor extends BaseEditorOption { + + constructor() { + const defaults: EditorDropIntoEditorOptions = { enabled: true }; + super( + EditorOption.dropIntoEditor, 'dropIntoEditor', defaults, + { + 'editor.dropIntoEditor.enabled': { + type: 'boolean', + default: defaults.enabled, + markdownDescription: nls.localize('dropIntoEditor.enabled', "Controls whether you can drag and drop a file into a text editor by holding down `shift` (instead of opening the file in an editor)."), + }, + } + ); + } + + public validate(_input: any): EditorDropIntoEditorOptions { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IDropIntoEditorOptions; + return { + enabled: boolean(input.enabled, this.defaultValue.enabled) + }; + } +} + +//#endregion + const DEFAULT_WINDOWS_FONT_FAMILY = 'Consolas, \'Courier New\', monospace'; const DEFAULT_MAC_FONT_FAMILY = 'Menlo, Monaco, \'Courier New\', monospace'; const DEFAULT_LINUX_FONT_FAMILY = '\'Droid Sans Mono\', \'monospace\', monospace'; @@ -4449,8 +4551,9 @@ export const enum EditorOption { disableMonospaceOptimizations, domReadOnly, dragAndDrop, - enableDropIntoEditor, + dropIntoEditor, emptySelectionClipboard, + experimental, extraEditorClassName, fastScrollSensitivity, find, @@ -4758,9 +4861,8 @@ export const EditorOptions = { { description: nls.localize('dragAndDrop', "Controls whether the editor should allow moving selections via drag and drop.") } )), emptySelectionClipboard: register(new EditorEmptySelectionClipboard()), - enableDropIntoEditor: register(new EditorBooleanOption( - EditorOption.enableDropIntoEditor, 'enableDropIntoEditor', true - )), + dropIntoEditor: register(new EditorDropIntoEditor()), + experimental: register(new EditorExperimental()), extraEditorClassName: register(new EditorStringOption( EditorOption.extraEditorClassName, 'extraEditorClassName', '', )), diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 10baf2667c9..a3d57455de4 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -669,9 +669,6 @@ export interface CodeAction { disabled?: string; } -/** - * @internal - */ export const enum CodeActionTriggerType { Invoke = 1, Auto = 2, @@ -1132,7 +1129,11 @@ export interface DocumentSymbolProvider { provideDocumentSymbols(model: model.ITextModel, token: CancellationToken): ProviderResult; } -export type TextEdit = { range: IRange; text: string; eol?: model.EndOfLineSequence }; +export interface TextEdit { + range: IRange; + text: string; + eol?: model.EndOfLineSequence; +} /** * Interface used to format a model diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 354026fdcb5..b6361586bd8 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -528,13 +528,18 @@ export const enum TrackedRangeStickiness { * Text snapshot that works like an iterator. * Will try to return chunks of roughly ~64KB size. * Will return null when finished. - * - * @internal */ export interface ITextSnapshot { read(): string | null; } +/** + * @internal + */ +export function isITextSnapshot(obj: any): obj is ITextSnapshot { + return (obj && typeof obj.read === 'function'); +} + /** * A model. */ @@ -610,7 +615,7 @@ export interface ITextModel { /** * Replace the entire text buffer value contained in this model. */ - setValue(newValue: string): void; + setValue(newValue: string | ITextSnapshot): void; /** * Get the text stored in this model. @@ -624,7 +629,6 @@ export interface ITextModel { * Get the text stored in this model. * @param preserverBOM Preserve a BOM character if it was detected when the model was constructed. * @return The text snapshot (it is safe to consume it asynchronously). - * @internal */ createSnapshot(preserveBOM?: boolean): ITextSnapshot; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 690a2eab09a..59675e6d0c6 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -44,12 +44,8 @@ import { ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextMod import { IColorTheme, ThemeColor } from 'vs/platform/theme/common/themeService'; import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRedo/common/undoRedo'; -function createTextBufferBuilder() { - return new PieceTreeTextBufferBuilder(); -} - export function createTextBufferFactory(text: string): model.ITextBufferFactory { - const builder = createTextBufferBuilder(); + const builder = new PieceTreeTextBufferBuilder(); builder.acceptChunk(text); return builder.finish(); } @@ -65,7 +61,7 @@ export function createTextBufferFactoryFromStream(stream: ITextStream): Promise< export function createTextBufferFactoryFromStream(stream: VSBufferReadableStream): Promise; export function createTextBufferFactoryFromStream(stream: ITextStream | VSBufferReadableStream): Promise { return new Promise((resolve, reject) => { - const builder = createTextBufferBuilder(); + const builder = new PieceTreeTextBufferBuilder(); let done = false; @@ -90,7 +86,7 @@ export function createTextBufferFactoryFromStream(stream: ITextStream | VSBuffer } export function createTextBufferFactoryFromSnapshot(snapshot: model.ITextSnapshot): model.ITextBufferFactory { - const builder = createTextBufferBuilder(); + const builder = new PieceTreeTextBufferBuilder(); let chunk: string | null; while (typeof (chunk = snapshot.read()) === 'string') { @@ -100,8 +96,15 @@ export function createTextBufferFactoryFromSnapshot(snapshot: model.ITextSnapsho return builder.finish(); } -export function createTextBuffer(value: string | model.ITextBufferFactory, defaultEOL: model.DefaultEndOfLine): { textBuffer: model.ITextBuffer; disposable: IDisposable } { - const factory = (typeof value === 'string' ? createTextBufferFactory(value) : value); +export function createTextBuffer(value: string | model.ITextBufferFactory | model.ITextSnapshot, defaultEOL: model.DefaultEndOfLine): { textBuffer: model.ITextBuffer; disposable: IDisposable } { + let factory: model.ITextBufferFactory; + if (typeof value === 'string') { + factory = createTextBufferFactory(value); + } else if (model.isITextSnapshot(value)) { + factory = createTextBufferFactoryFromSnapshot(value); + } else { + factory = value; + } return factory.create(defaultEOL); } @@ -424,7 +427,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati this._eventEmitter.fire(new InternalModelContentChangeEvent(rawChange, change)); } - public setValue(value: string): void { + public setValue(value: string | model.ITextSnapshot): void { this._assertNotDisposed(); if (value === null) { // There's nothing to do diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index 57a63980ce7..38f13bf83c7 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -71,7 +71,6 @@ export function getIconClasses(modelService: IModelService, languageService: ILa return classes; } - export function getIconClassesForLanguageId(languageId: string): string[] { return ['file-icon', `${cssEscape(languageId)}-lang-file-icon`]; } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 8d709286d5c..5b9c0195e7b 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -15,6 +15,11 @@ export enum AccessibilitySupport { Enabled = 2 } +export enum CodeActionTriggerType { + Invoke = 1, + Auto = 2 +} + export enum CompletionItemInsertTextRule { /** * Adjust whitespace/indentation of multiline insert texts to @@ -199,108 +204,109 @@ export enum EditorOption { disableMonospaceOptimizations = 29, domReadOnly = 30, dragAndDrop = 31, - enableDropIntoEditor = 32, + dropIntoEditor = 32, emptySelectionClipboard = 33, - extraEditorClassName = 34, - fastScrollSensitivity = 35, - find = 36, - fixedOverflowWidgets = 37, - folding = 38, - foldingStrategy = 39, - foldingHighlight = 40, - foldingImportsByDefault = 41, - foldingMaximumRegions = 42, - unfoldOnClickAfterEndOfLine = 43, - fontFamily = 44, - fontInfo = 45, - fontLigatures = 46, - fontSize = 47, - fontWeight = 48, - formatOnPaste = 49, - formatOnType = 50, - glyphMargin = 51, - gotoLocation = 52, - hideCursorInOverviewRuler = 53, - hover = 54, - inDiffEditor = 55, - inlineSuggest = 56, - letterSpacing = 57, - lightbulb = 58, - lineDecorationsWidth = 59, - lineHeight = 60, - lineNumbers = 61, - lineNumbersMinChars = 62, - linkedEditing = 63, - links = 64, - matchBrackets = 65, - minimap = 66, - mouseStyle = 67, - mouseWheelScrollSensitivity = 68, - mouseWheelZoom = 69, - multiCursorMergeOverlapping = 70, - multiCursorModifier = 71, - multiCursorPaste = 72, - occurrencesHighlight = 73, - overviewRulerBorder = 74, - overviewRulerLanes = 75, - padding = 76, - parameterHints = 77, - peekWidgetDefaultFocus = 78, - definitionLinkOpensInPeek = 79, - quickSuggestions = 80, - quickSuggestionsDelay = 81, - readOnly = 82, - renameOnType = 83, - renderControlCharacters = 84, - renderFinalNewline = 85, - renderLineHighlight = 86, - renderLineHighlightOnlyWhenFocus = 87, - renderValidationDecorations = 88, - renderWhitespace = 89, - revealHorizontalRightPadding = 90, - roundedSelection = 91, - rulers = 92, - scrollbar = 93, - scrollBeyondLastColumn = 94, - scrollBeyondLastLine = 95, - scrollPredominantAxis = 96, - selectionClipboard = 97, - selectionHighlight = 98, - selectOnLineNumbers = 99, - showFoldingControls = 100, - showUnused = 101, - snippetSuggestions = 102, - smartSelect = 103, - smoothScrolling = 104, - stickyTabStops = 105, - stopRenderingLineAfter = 106, - suggest = 107, - suggestFontSize = 108, - suggestLineHeight = 109, - suggestOnTriggerCharacters = 110, - suggestSelection = 111, - tabCompletion = 112, - tabIndex = 113, - unicodeHighlighting = 114, - unusualLineTerminators = 115, - useShadowDOM = 116, - useTabStops = 117, - wordSeparators = 118, - wordWrap = 119, - wordWrapBreakAfterCharacters = 120, - wordWrapBreakBeforeCharacters = 121, - wordWrapColumn = 122, - wordWrapOverride1 = 123, - wordWrapOverride2 = 124, - wrappingIndent = 125, - wrappingStrategy = 126, - showDeprecated = 127, - inlayHints = 128, - editorClassName = 129, - pixelRatio = 130, - tabFocusMode = 131, - layoutInfo = 132, - wrappingInfo = 133 + experimental = 34, + extraEditorClassName = 35, + fastScrollSensitivity = 36, + find = 37, + fixedOverflowWidgets = 38, + folding = 39, + foldingStrategy = 40, + foldingHighlight = 41, + foldingImportsByDefault = 42, + foldingMaximumRegions = 43, + unfoldOnClickAfterEndOfLine = 44, + fontFamily = 45, + fontInfo = 46, + fontLigatures = 47, + fontSize = 48, + fontWeight = 49, + formatOnPaste = 50, + formatOnType = 51, + glyphMargin = 52, + gotoLocation = 53, + hideCursorInOverviewRuler = 54, + hover = 55, + inDiffEditor = 56, + inlineSuggest = 57, + letterSpacing = 58, + lightbulb = 59, + lineDecorationsWidth = 60, + lineHeight = 61, + lineNumbers = 62, + lineNumbersMinChars = 63, + linkedEditing = 64, + links = 65, + matchBrackets = 66, + minimap = 67, + mouseStyle = 68, + mouseWheelScrollSensitivity = 69, + mouseWheelZoom = 70, + multiCursorMergeOverlapping = 71, + multiCursorModifier = 72, + multiCursorPaste = 73, + occurrencesHighlight = 74, + overviewRulerBorder = 75, + overviewRulerLanes = 76, + padding = 77, + parameterHints = 78, + peekWidgetDefaultFocus = 79, + definitionLinkOpensInPeek = 80, + quickSuggestions = 81, + quickSuggestionsDelay = 82, + readOnly = 83, + renameOnType = 84, + renderControlCharacters = 85, + renderFinalNewline = 86, + renderLineHighlight = 87, + renderLineHighlightOnlyWhenFocus = 88, + renderValidationDecorations = 89, + renderWhitespace = 90, + revealHorizontalRightPadding = 91, + roundedSelection = 92, + rulers = 93, + scrollbar = 94, + scrollBeyondLastColumn = 95, + scrollBeyondLastLine = 96, + scrollPredominantAxis = 97, + selectionClipboard = 98, + selectionHighlight = 99, + selectOnLineNumbers = 100, + showFoldingControls = 101, + showUnused = 102, + snippetSuggestions = 103, + smartSelect = 104, + smoothScrolling = 105, + stickyTabStops = 106, + stopRenderingLineAfter = 107, + suggest = 108, + suggestFontSize = 109, + suggestLineHeight = 110, + suggestOnTriggerCharacters = 111, + suggestSelection = 112, + tabCompletion = 113, + tabIndex = 114, + unicodeHighlighting = 115, + unusualLineTerminators = 116, + useShadowDOM = 117, + useTabStops = 118, + wordSeparators = 119, + wordWrap = 120, + wordWrapBreakAfterCharacters = 121, + wordWrapBreakBeforeCharacters = 122, + wordWrapColumn = 123, + wordWrapOverride1 = 124, + wordWrapOverride2 = 125, + wrappingIndent = 126, + wrappingStrategy = 127, + showDeprecated = 128, + inlayHints = 129, + editorClassName = 130, + pixelRatio = 131, + tabFocusMode = 132, + layoutInfo = 133, + wrappingInfo = 134 } /** diff --git a/src/vs/editor/common/viewLayout/viewLinesViewportData.ts b/src/vs/editor/common/viewLayout/viewLinesViewportData.ts index de06a61132e..8ddcfddb99d 100644 --- a/src/vs/editor/common/viewLayout/viewLinesViewportData.ts +++ b/src/vs/editor/common/viewLayout/viewLinesViewportData.ts @@ -70,7 +70,7 @@ export class ViewportData { } public getViewLineRenderingData(lineNumber: number): ViewLineRenderingData { - return this._model.getViewLineRenderingData(this.visibleRange, lineNumber); + return this._model.getViewportViewLineRenderingData(this.visibleRange, lineNumber); } public getDecorationsInViewport(): ViewModelDecoration[] { diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index e582f7dc719..55f613af360 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -41,12 +41,15 @@ export interface IViewModel extends ICursorSimpleModel { onCompositionEnd(): void; getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; - getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; + getViewportViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; + getViewLineRenderingData(lineNumber: number): ViewLineRenderingData; getViewLineData(lineNumber: number): ViewLineData; getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData; getCompletelyVisibleViewRange(): Range; getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range; + getHiddenAreas(): Range[]; + getLineCount(): number; getLineContent(lineNumber: number): string; getLineLength(lineNumber: number): number; diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index 909d9f19bc3..25edc82d950 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -100,17 +100,21 @@ export class ViewModelDecorations implements IDisposable { let cacheIsValid = (this._cachedModelDecorationsResolver !== null); cacheIsValid = cacheIsValid && (viewRange.equalsRange(this._cachedModelDecorationsResolverViewRange)); if (!cacheIsValid) { - this._cachedModelDecorationsResolver = this._getDecorationsViewportData(viewRange); + this._cachedModelDecorationsResolver = this._getDecorationsInRange(viewRange); this._cachedModelDecorationsResolverViewRange = viewRange; } return this._cachedModelDecorationsResolver!; } - private _getDecorationsViewportData(viewportRange: Range): IDecorationsViewportData { - const modelDecorations = this._linesCollection.getDecorationsInRange(viewportRange, this.editorId, filterValidationDecorations(this.configuration.options)); + public getInlineDecorationsOnLine(lineNumber: number): InlineDecoration[] { + const range = new Range(lineNumber, this._linesCollection.getViewLineMinColumn(lineNumber), lineNumber, this._linesCollection.getViewLineMaxColumn(lineNumber)); + return this._getDecorationsInRange(range).inlineDecorations[0]; + } - const startLineNumber = viewportRange.startLineNumber; - const endLineNumber = viewportRange.endLineNumber; + private _getDecorationsInRange(viewRange: Range): IDecorationsViewportData { + const modelDecorations = this._linesCollection.getDecorationsInRange(viewRange, this.editorId, filterValidationDecorations(this.configuration.options)); + const startLineNumber = viewRange.startLineNumber; + const endLineNumber = viewRange.endLineNumber; const decorationsInViewport: ViewModelDecoration[] = []; let decorationsInViewportLen = 0; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index c55cf3d02d1..a241643468e 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -34,9 +34,9 @@ import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; import { ILineBreaksComputer, ILineBreaksComputerFactory, InjectedText } from 'vs/editor/common/modelLineProjectionData'; import { ViewEventHandler } from 'vs/editor/common/viewEventHandler'; -import { ICoordinatesConverter, IViewModel, IWhitespaceChangeAccessor, MinimapLinesRenderingData, OverviewRulerDecorationsGroup, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel'; +import { ICoordinatesConverter, InlineDecoration, IViewModel, IWhitespaceChangeAccessor, MinimapLinesRenderingData, OverviewRulerDecorationsGroup, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; -import { FocusChangedEvent, ModelContentChangedEvent, ModelDecorationsChangedEvent, ModelLanguageChangedEvent, ModelLanguageConfigurationChangedEvent, ModelOptionsChangedEvent, ModelTokensChangedEvent, OutgoingViewModelEvent, ReadOnlyEditAttemptEvent, ScrollChangedEvent, ViewModelEventDispatcher, ViewModelEventsCollector, ViewZonesChangedEvent } from 'vs/editor/common/viewModelEventDispatcher'; +import { FocusChangedEvent, HiddenAreasChangedEvent, ModelContentChangedEvent, ModelDecorationsChangedEvent, ModelLanguageChangedEvent, ModelLanguageConfigurationChangedEvent, ModelOptionsChangedEvent, ModelTokensChangedEvent, OutgoingViewModelEvent, ReadOnlyEditAttemptEvent, ScrollChangedEvent, ViewModelEventDispatcher, ViewModelEventsCollector, ViewZonesChangedEvent } from 'vs/editor/common/viewModelEventDispatcher'; import { IViewModelLines, ViewModelLinesFromModelAsIs, ViewModelLinesFromProjectedModel } from 'vs/editor/common/viewModel/viewModelLines'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -485,7 +485,7 @@ export class ViewModel extends Disposable implements IViewModel { this._updateConfigurationViewLineCount.schedule(); if (lineMappingChanged) { - this._eventDispatcher.emitOutgoingEvent(new ViewZonesChangedEvent()); + this._eventDispatcher.emitOutgoingEvent(new HiddenAreasChangedEvent()); } } @@ -508,6 +508,10 @@ export class ViewModel extends Disposable implements IViewModel { return this._toModelVisibleRanges(visibleViewRange); } + public getHiddenAreas(): Range[] { + return this._lines.getHiddenAreas(); + } + private _toModelVisibleRanges(visibleViewRange: Range): Range[] { const visibleRange = this.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange); const hiddenAreas = this._lines.getHiddenAreas(); @@ -679,13 +683,22 @@ export class ViewModel extends Disposable implements IViewModel { return this._lines.getInjectedTextAt(viewPosition); } - public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData { + public getViewportViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData { + const allInlineDecorations = this._decorations.getDecorationsViewportData(visibleRange).inlineDecorations; + const inlineDecorations = allInlineDecorations[lineNumber - visibleRange.startLineNumber]; + return this._getViewLineRenderingData(lineNumber, inlineDecorations); + } + + public getViewLineRenderingData(lineNumber: number): ViewLineRenderingData { + const inlineDecorations = this._decorations.getInlineDecorationsOnLine(lineNumber); + return this._getViewLineRenderingData(lineNumber, inlineDecorations); + } + + private _getViewLineRenderingData(lineNumber: number, inlineDecorations: InlineDecoration[]): ViewLineRenderingData { const mightContainRTL = this.model.mightContainRTL(); const mightContainNonBasicASCII = this.model.mightContainNonBasicASCII(); const tabSize = this.getTabSize(); const lineData = this._lines.getViewLineData(lineNumber); - const allInlineDecorations = this._decorations.getDecorationsViewportData(visibleRange).inlineDecorations; - let inlineDecorations = allInlineDecorations[lineNumber - visibleRange.startLineNumber]; if (lineData.inlineDecorations) { inlineDecorations = [ diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 62647863cf5..7f106ceba40 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -151,6 +151,13 @@ export class QuickFixController extends Disposable implements IEditorContributio } } + public selectedOptionWithPreview() { + if (this._ui.hasValue()) { + this._ui.getValue().onPreviewEnter(); + } + + } + public showCodeActions(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) { return this._ui.getValue().showCodeActionList(trigger, actions, at, { includeDisabledActions: false, fromLightbulb: false }); } @@ -566,4 +573,16 @@ registerEditorCommand(new CodeActionContribution({ } })); +registerEditorCommand(new CodeActionContribution({ + id: 'onEnterSelectCodeActionWithPreview', + precondition: Context.Visible, + handler(x) { + x.selectedOptionWithPreview(); + }, + kbOpts: { + weight: weight + 100000, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + } +})); + diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index a2d66113907..aa51bc780af 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { IListEvent, IListMouseEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; @@ -69,6 +69,7 @@ export interface ICodeActionMenuItem { decoratorRight?: string; isSeparator?: boolean; isEnabled: boolean; + isDocumentation: boolean; index: number; disposables?: IDisposable[]; } @@ -93,6 +94,12 @@ const TEMPLATE_ID = 'codeActionWidget'; const codeActionLineHeight = 26; class CodeMenuRenderer implements IListRenderer { + + constructor( + private readonly acceptKeybindings: [string, string], + @IKeybindingService private readonly keybindingService: IKeybindingService, + ) { } + get templateId(): string { return TEMPLATE_ID; } renderTemplate(container: HTMLElement): ICodeActionMenuTemplateData { @@ -114,6 +121,7 @@ class CodeMenuRenderer implements IListRenderer { + const [accept, preview] = this.acceptKeybindings; + data.root.title = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Refactor, Shift+F2 to Preview"'] }, "{0} to Refactor, {1} to Preview", this.keybindingService.lookupKeybinding(accept)?.getLabel(), this.keybindingService.lookupKeybinding(preview)?.getLabel()); + // data.root.title = this.keybindingService.lookupKeybinding(accept)?.getLabel() + ' to Refactor, ' + this.keybindingService.lookupKeybinding(preview)?.getLabel() + ' to Preview'; + }; + updateLabel(); + } + } disposeTemplate(templateData: ICodeActionMenuTemplateData): void { templateData.disposables = dispose(templateData.disposables); @@ -144,8 +161,11 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { private _ctxMenuWidgetVisible: IContextKey; private viewItems: ICodeActionMenuItem[] = []; private focusedEnabledItem: number | undefined; - private currSelectedItem: number = 0; + private currSelectedItem: number | undefined; private hasSeperator: boolean = false; + private block?: HTMLElement; + + public static readonly documentationID: string = '_documentation'; public static readonly ID: string = 'editor.contrib.codeActionMenu'; @@ -154,7 +174,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { } private readonly _keybindingResolver: CodeActionKeybindingResolver; - private listRenderer: CodeMenuRenderer = new CodeMenuRenderer(); + private listRenderer: CodeMenuRenderer; constructor( private readonly _editor: ICodeEditor, @@ -175,6 +195,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { }); this._ctxMenuWidgetVisible = Context.Visible.bindTo(this._contextKeyService); + this.listRenderer = new CodeMenuRenderer([`onEnterSelectCodeAction`, `onEnterSelectCodeActionWithPreview`], keybindingService); } get isVisible(): boolean { @@ -192,17 +213,46 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { e.elements.forEach(element => { if (element.isEnabled) { element.action.run(); + this.hideCodeActionWidget(); } }); - this.hideCodeActionWidget(); } } + private _onListHover(e: IListMouseEvent): void { + if (!e.element) { + this.currSelectedItem = undefined; + this.codeActionList.value?.setFocus([]); + } else { + if (e.element?.isEnabled) { + this.codeActionList.value?.setFocus([e.element.index]); + this.focusedEnabledItem = this.viewItems.indexOf(e.element); + this.currSelectedItem = e.element.index; + } else { + this.currSelectedItem = undefined; + this.codeActionList.value?.setFocus([e.element.index]); + } + } + } private renderCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): IDisposable { const renderDisposables = new DisposableStore(); const renderMenu = document.createElement('div'); + // Render invisible div to block mouse interaction in the rest of the UI + const menuBlock = document.createElement('div'); + this.block = element.appendChild(menuBlock); + this.block.classList.add('context-view-block'); + this.block.style.position = 'fixed'; + this.block.style.cursor = 'initial'; + this.block.style.left = '0'; + this.block.style.top = '0'; + this.block.style.width = '100%'; + this.block.style.height = '100%'; + this.block.style.zIndex = '-1'; + + renderDisposables.add(dom.addDisposableListener(this.block, dom.EventType.MOUSE_DOWN, e => e.stopPropagation())); + renderMenu.id = 'codeActionMenuWidget'; renderMenu.classList.add('codeActionMenuWidget'); @@ -221,6 +271,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { }, [this.listRenderer], { keyboardSupport: false } ); + renderDisposables.add(this.codeActionList.value.onMouseOver(e => this._onListHover(e))); + renderDisposables.add(this.codeActionList.value.onDidChangeFocus(e => this.codeActionList.value?.domFocus())); renderDisposables.add(this.codeActionList.value.onDidChangeSelection(e => this._onListSelection(e))); renderDisposables.add(this._editor.onDidLayoutChange(e => this.hideCodeActionWidget())); @@ -228,11 +280,16 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // Populating the list widget and tracking enabled options. inputArray.forEach((item, index) => { const currIsSeparator = item.class === 'separator'; + let isDocumentation = false; + if (item instanceof CodeActionAction) { + isDocumentation = item.action.kind === CodeActionMenu.documentationID; + } + if (currIsSeparator) { // set to true forever this.hasSeperator = true; } - const menuItem = { title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: currIsSeparator, index }; + const menuItem = { title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: currIsSeparator, index, isDocumentation }; if (item.enabled) { this.viewItems.push(menuItem); } @@ -263,9 +320,13 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.codeActionList.value?.layout(height, maxWidth); // List selection - this.focusedEnabledItem = 0; - this.currSelectedItem = this.viewItems[0].index; - this.codeActionList.value.setFocus([this.currSelectedItem]); + if (this.viewItems.length < 1 || this.viewItems.every(item => item.isDocumentation)) { + this.currSelectedItem = undefined; + } else { + this.focusedEnabledItem = 0; + this.currSelectedItem = this.viewItems[0].index; + this.codeActionList.value.setFocus([this.currSelectedItem]); + } // List Focus this.codeActionList.value.domFocus(); @@ -284,7 +345,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { protected focusPrevious() { if (typeof this.focusedEnabledItem === 'undefined') { this.focusedEnabledItem = this.viewItems[0].index; - } else if (this.viewItems.length <= 1) { + } else if (this.viewItems.length < 1) { return false; } @@ -307,7 +368,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { protected focusNext() { if (typeof this.focusedEnabledItem === 'undefined') { this.focusedEnabledItem = this.viewItems.length - 1; - } else if (this.viewItems.length <= 1) { + } else if (this.viewItems.length < 1) { return false; } @@ -333,7 +394,9 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { } public onEnterSet() { - this.codeActionList.value?.setSelection([this.currSelectedItem]); + if (typeof this.currSelectedItem === 'number') { + this.codeActionList.value?.setSelection([this.currSelectedItem]); + } } override dispose() { @@ -345,7 +408,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.options = []; this.viewItems = []; this.focusedEnabledItem = 0; - this.currSelectedItem = 0; + this.currSelectedItem = undefined; this.hasSeperator = false; this._contextViewService.hideContextView({ source: this }); } @@ -455,6 +518,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { result.push(new Separator(), ...allDocumentation.map(command => toCodeActionAction(new CodeActionItem({ title: command.title, command: command, + kind: CodeActionMenu.documentationID }, undefined)))); } diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index e42d043ab5e..f8ac0210a73 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -23,6 +23,7 @@ export class CodeActionUi extends Disposable { private readonly _codeActionWidget: Lazy; private readonly _lightBulbWidget: Lazy; private readonly _activeCodeActions = this._register(new MutableDisposable()); + private previewOn: boolean = false; #disposed = false; @@ -40,7 +41,12 @@ export class CodeActionUi extends Disposable { this._codeActionWidget = new Lazy(() => { return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, { onSelectCodeAction: async (action, trigger) => { - this.delegate.applyCodeAction(action, /* retrigger */ true, Boolean(trigger.preview)); + if (this.previewOn) { + this.delegate.applyCodeAction(action, /* retrigger */ true, Boolean(this.previewOn)); + } else { + this.delegate.applyCodeAction(action, /* retrigger */ true, Boolean(trigger.preview)); + } + this.previewOn = false; } })); }); @@ -70,6 +76,11 @@ export class CodeActionUi extends Disposable { } } + public onPreviewEnter() { + this.previewOn = true; + this.onEnter(); + } + public navigateList(navUp: Boolean) { if (this._codeActionWidget.hasValue()) { if (navUp) { diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index 4068820413d..52faf076802 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -18,6 +18,7 @@ border-color: none; background-color: var(--vscode-menu-background); color: var(--vscode-menu-foreground); + box-shadow: rgb(0,0,0, 16%) 0px 2px 8px; } .codeActionMenuWidget .monaco-list:not(.element-focused):focus:before { @@ -29,10 +30,10 @@ z-index: 5; /* make sure we are on top of the tree items */ content: ""; pointer-events: none; /* enable click through */ - outline: 0px solid; /* we still need to handle the empty tree or no focus item case */ - outline-width: 0px; - outline-style: none; - outline-offset: 0px; + outline: 0px solid !important; /* we still need to handle the empty tree or no focus item case */ + outline-width: 0px !important; + outline-style: none !important; + outline-offset: 0px !important; } .codeActionMenuWidget .monaco-list { diff --git a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index 0cd4c1e73b0..650488e797f 100644 --- a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -311,7 +311,7 @@ export class ContextMenuController implements IEditorContribution { const actions: IAction[] = []; actions.push(createAction({ - label: nls.localize('context.minimap.showMinimap', "Show Minimap"), + label: nls.localize('context.minimap.minimap', "Minimap"), checked: minimapOptions.enabled, run: () => { this._configurationService.updateValue(`editor.minimap.enabled`, !minimapOptions.enabled); diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index 10ed525fc05..83f8ad1e314 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -25,7 +25,6 @@ import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/edit import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { localize } from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -37,7 +36,6 @@ export class DropIntoEditorController extends Disposable implements IEditorContr constructor( editor: ICodeEditor, @IBulkEditService private readonly _bulkEditService: IBulkEditService, - @IConfigurationService private readonly _configurationService: IConfigurationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IProgressService private readonly _progressService: IProgressService, @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @@ -46,22 +44,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr this._register(editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event))); - this._languageFeaturesService.documentOnDropEditProvider.register('*', new DefaultOnDropProvider(workspaceContextService)); - - this._register(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('workbench.editor.dropIntoEditor.enabled')) { - this.updateEditorOptions(editor); - } - })); - - this.updateEditorOptions(editor); - } - - private updateEditorOptions(editor: ICodeEditor) { - editor.updateOptions({ - enableDropIntoEditor: this._configurationService.getValue('workbench.editor.dropIntoEditor.enabled') - }); } private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) { diff --git a/src/vs/editor/contrib/folding/browser/folding.css b/src/vs/editor/contrib/folding/browser/folding.css index a68daff8155..4f70d1097fe 100644 --- a/src/vs/editor/contrib/folding/browser/folding.css +++ b/src/vs/editor/contrib/folding/browser/folding.css @@ -2,7 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - +.monaco-editor .margin-view-overlays .codicon-folding-manual-collapsed, +.monaco-editor .margin-view-overlays .codicon-folding-manual-expanded, .monaco-editor .margin-view-overlays .codicon-folding-expanded, .monaco-editor .margin-view-overlays .codicon-folding-collapsed { cursor: pointer; @@ -17,6 +18,7 @@ .monaco-editor .margin-view-overlays:hover .codicon, .monaco-editor .margin-view-overlays .codicon.codicon-folding-collapsed, +.monaco-editor .margin-view-overlays .codicon.codicon-folding-manual-collapsed, .monaco-editor .margin-view-overlays .codicon.alwaysShowFoldIcons { opacity: 1; } @@ -29,3 +31,4 @@ line-height: 1em; cursor: pointer; } + diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index 64339450d95..c35cdb69a65 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -31,8 +31,8 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { editorSelectionBackground, iconForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon, foldingManualIcon } from './foldingDecorations'; -import { FoldingRegion, FoldingRegions, FoldRange } from './foldingRanges'; +import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon, foldingManualCollapsedIcon, foldingManualExpandedIcon } from './foldingDecorations'; +import { FoldingRegion, FoldingRegions, FoldRange, FoldSource, ILineRange } from './foldingRanges'; import { SyntaxRangeProvider } from './syntaxRangeProvider'; import { INotificationService } from 'vs/platform/notification/common/notification'; import Severity from 'vs/base/common/severity'; @@ -222,7 +222,7 @@ export class FoldingController extends Disposable implements IEditorContribution } this._currentModelHasFoldedImports = false; - this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider, this.triggerFoldingModelChanged.bind(this)); + this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider); this.localToDispose.add(this.foldingModel); this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel); @@ -293,7 +293,7 @@ export class FoldingController extends Disposable implements IEditorContribution this.triggerFoldingModelChanged(); } - private triggerFoldingModelChanged() { + public triggerFoldingModelChanged() { if (this.updateScheduler) { if (this.foldingRegionPromise) { this.foldingRegionPromise.cancel(); @@ -1066,17 +1066,17 @@ class GotoNextFoldAction extends FoldingAction { } } -class FoldSelectedAction extends FoldingAction { +class FoldRangeFromSelectionAction extends FoldingAction { constructor() { super({ - id: 'editor.foldSelected', - label: nls.localize('foldSelectedAction.label', "Fold Selected Lines"), - alias: 'Fold Selected Lines', + id: 'editor.createFoldingRangeFromSelection', + label: nls.localize('createManualFoldRange.label', "Create Manual Folding Range from Selection"), + alias: 'Create Folding Range from Selection', precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Period), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Comma), weight: KeybindingWeight.EditorContrib } }); @@ -1097,7 +1097,7 @@ class FoldSelectedAction extends FoldingAction { endLineNumber: endLineNumber, type: undefined, isCollapsed: true, - isManualSelection: true + source: FoldSource.userDefined }); editor.setSelection({ startLineNumber: selection.startLineNumber, @@ -1118,6 +1118,36 @@ class FoldSelectedAction extends FoldingAction { } } +class RemoveFoldRangeFromSelectionAction extends FoldingAction { + + constructor() { + super({ + id: 'editor.removeManualFoldingRanges', + label: nls.localize('removeManualFoldingRanges.label', "Remove Manual Folding Ranges"), + alias: 'Remove Manual Folding Ranges', + precondition: CONTEXT_FOLDING_ENABLED, + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Period), + weight: KeybindingWeight.EditorContrib + } + }); + } + + invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void { + const selections = editor.getSelections(); + if (selections) { + const ranges: ILineRange[] = []; + for (const selection of selections) { + const { startLineNumber, endLineNumber } = selection; + ranges.push(endLineNumber >= startLineNumber ? { startLineNumber, endLineNumber } : { endLineNumber, startLineNumber }); + } + foldingModel.removeManualRanges(ranges); + foldingController.triggerFoldingModelChanged(); + } + } +} + registerEditorContribution(FoldingController.ID, FoldingController); registerEditorAction(UnfoldAction); @@ -1135,7 +1165,8 @@ registerEditorAction(ToggleFoldAction); registerEditorAction(GotoParentFoldAction); registerEditorAction(GotoPreviousFoldAction); registerEditorAction(GotoNextFoldAction); -registerEditorAction(FoldSelectedAction); +registerEditorAction(FoldRangeFromSelectionAction); +registerEditorAction(RemoveFoldRangeFromSelectionAction); for (let i = 1; i <= 7; i++) { registerInstantiatedEditorAction( @@ -1167,7 +1198,8 @@ registerThemingParticipant((theme, collector) => { collector.addRule(` .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingExpandedIcon)}, .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)}, - .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingManualIcon)} { + .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingManualExpandedIcon)}, + .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingManualCollapsedIcon)} { color: ${editorFoldColor} !important; } `); diff --git a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts index 132dd19a9fa..413d0ae9a74 100644 --- a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts @@ -14,12 +14,14 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown, localize('foldingExpandedIcon', 'Icon for expanded ranges in the editor glyph margin.')); export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight, localize('foldingCollapsedIcon', 'Icon for collapsed ranges in the editor glyph margin.')); -export const foldingManualIcon = registerIcon('folding-manual', Codicon.ellipsis, localize('foldingManualIcon', 'Icon for manually collapsed ranges in the editor glyph margin.')); +export const foldingManualCollapsedIcon = registerIcon('folding-manual-collapsed', foldingCollapsedIcon, localize('foldingManualCollapedIcon', 'Icon for manually collapsed ranges in the editor glyph margin.')); +export const foldingManualExpandedIcon = registerIcon('folding-manual-expanded', foldingExpandedIcon, localize('foldingManualExpandedIcon', 'Icon for manually expanded ranges in the editor glyph margin.')); + export class FoldingDecorationProvider implements IDecorationProvider { private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ description: 'folding-collapsed-visual-decoration', - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', isWholeLine: true, firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon) @@ -27,7 +29,7 @@ export class FoldingDecorationProvider implements IDecorationProvider { private static readonly COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({ description: 'folding-collapsed-highlighted-visual-decoration', - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', className: 'folded-background', isWholeLine: true, @@ -36,19 +38,19 @@ export class FoldingDecorationProvider implements IDecorationProvider { private static readonly MANUALLY_COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ description: 'folding-manually-collapsed-visual-decoration', - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', isWholeLine: true, - firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualIcon) + firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingExpandedIcon) }); private static readonly MANUALLY_COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({ description: 'folding-manually-collapsed-highlighted-visual-decoration', - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', className: 'folded-background', isWholeLine: true, - firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualIcon) + firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualCollapsedIcon) }); private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ @@ -65,6 +67,21 @@ export class FoldingDecorationProvider implements IDecorationProvider { firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingExpandedIcon) }); + private static readonly MANUALLY_EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ + description: 'folding-manually-expanded-visual-decoration', + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, + isWholeLine: true, + firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingManualExpandedIcon) + }); + + private static readonly MANUALLY_EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ + description: 'folding-manually-expanded-visual-decoration', + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, + isWholeLine: true, + firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualExpandedIcon) + }); + + private static readonly HIDDEN_RANGE_DECORATION = ModelDecorationOptions.register({ description: 'folding-hidden-range-decoration', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges @@ -77,19 +94,19 @@ export class FoldingDecorationProvider implements IDecorationProvider { constructor(private readonly editor: ICodeEditor) { } - getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManualSelection: boolean): IModelDecorationOptions { + getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManual: boolean): IModelDecorationOptions { if (isHidden // is inside another collapsed region - || this.showFoldingControls === 'never' || (isManualSelection && !isCollapsed)) { // + || this.showFoldingControls === 'never') { return FoldingDecorationProvider.HIDDEN_RANGE_DECORATION; } if (isCollapsed) { - return isManualSelection ? + return isManual ? (this.showFoldingHighlights ? FoldingDecorationProvider.MANUALLY_COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.MANUALLY_COLLAPSED_VISUAL_DECORATION) : (this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION); } else if (this.showFoldingControls === 'mouseover') { - return FoldingDecorationProvider.EXPANDED_AUTO_HIDE_VISUAL_DECORATION; + return isManual ? FoldingDecorationProvider.MANUALLY_EXPANDED_AUTO_HIDE_VISUAL_DECORATION : FoldingDecorationProvider.EXPANDED_AUTO_HIDE_VISUAL_DECORATION; } else { - return FoldingDecorationProvider.EXPANDED_VISUAL_DECORATION; + return isManual ? FoldingDecorationProvider.MANUALLY_EXPANDED_VISUAL_DECORATION : FoldingDecorationProvider.EXPANDED_VISUAL_DECORATION; } } diff --git a/src/vs/editor/contrib/folding/browser/foldingModel.ts b/src/vs/editor/contrib/folding/browser/foldingModel.ts index 59083ae1d3e..936e83b605f 100644 --- a/src/vs/editor/contrib/folding/browser/foldingModel.ts +++ b/src/vs/editor/contrib/folding/browser/foldingModel.ts @@ -5,11 +5,11 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { FoldingRegion, FoldingRegions, ILineRange, FoldRange } from './foldingRanges'; +import { FoldingRegion, FoldingRegions, ILineRange, FoldRange, FoldSource } from './foldingRanges'; import { hash } from 'vs/base/common/hash'; export interface IDecorationProvider { - getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManualSelection: boolean): IModelDecorationOptions; + getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManual: boolean): IModelDecorationOptions; changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null; removeDecorations(decorationIds: string[]): void; } @@ -21,6 +21,8 @@ export interface FoldingModelChangeEvent { interface ILineMemento extends ILineRange { checksum?: number; + isCollapsed?: boolean; + source?: FoldSource; } export type CollapseMemento = ILineMemento[]; @@ -28,7 +30,6 @@ export type CollapseMemento = ILineMemento[]; export class FoldingModel { private readonly _textModel: ITextModel; private readonly _decorationProvider: IDecorationProvider; - private readonly _triggerRecomputeRanges: (() => void) | undefined; private _regions: FoldingRegions; private _editorDecorationIds: string[]; @@ -40,10 +41,9 @@ export class FoldingModel { public get textModel() { return this._textModel; } public get decorationProvider() { return this._decorationProvider; } - constructor(textModel: ITextModel, decorationProvider: IDecorationProvider, triggerRecomputeRanges?: () => void) { + constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) { this._textModel = textModel; this._decorationProvider = decorationProvider; - this._triggerRecomputeRanges = triggerRecomputeRanges; this._regions = new FoldingRegions(new Uint32Array(0), new Uint32Array(0)); this._editorDecorationIds = []; } @@ -55,7 +55,6 @@ export class FoldingModel { toggledRegions = toggledRegions.sort((r1, r2) => r1.regionIndex - r2.regionIndex); const processed: { [key: string]: boolean | undefined } = {}; - const manualExpanded = false; this._decorationProvider.changeDecorations(accessor => { let k = 0; // index from [0 ... this.regions.length] let dirtyRegionEndLine = -1; // end of the range where decorations need to be updated @@ -64,9 +63,9 @@ export class FoldingModel { while (k < index) { const endLineNumber = this._regions.getEndLineNumber(k); const isCollapsed = this._regions.isCollapsed(k); - const isManualSelection = this.regions.isManualSelection(k); if (endLineNumber <= dirtyRegionEndLine) { - accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManualSelection)); + const isManual = this.regions.getSource(k) !== FoldSource.provider; + accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManual)); } if (isCollapsed && endLineNumber > lastHiddenLine) { lastHiddenLine = endLineNumber; @@ -91,16 +90,30 @@ export class FoldingModel { updateDecorationsUntil(this._regions.length); }); this._updateEventEmitter.fire({ model: this, collapseStateChanged: toggledRegions }); - if (manualExpanded && this._triggerRecomputeRanges) { - // expanding a range which didn't originate from range provider might now enable ranges - // from the provider which were previously dropped due to the collapsed range - this._triggerRecomputeRanges(); + } + + public removeManualRanges(ranges: ILineRange[]) { + const newFoldingRanges: FoldRange[] = new Array(); + const intersects = (foldRange: FoldRange) => { + for (const range of ranges) { + if (!(range.startLineNumber > foldRange.endLineNumber || foldRange.startLineNumber > range.endLineNumber)) { + return true; + } + } + return false; + }; + for (let i = 0; i < this._regions.length; i++) { + const foldRange = this._regions.toFoldRange(i); + if (foldRange.source === FoldSource.provider || !intersects(foldRange)) { + newFoldingRanges.push(foldRange); + } } + this.updatePost(FoldingRegions.fromFoldRanges(newFoldingRanges)); } public update(newRegions: FoldingRegions, blockedLineNumers: number[] = []): void { - const hiddenRanges = this._currentHiddenRegions(blockedLineNumers); - const newRanges = FoldingRegions.sanitizeAndMerge(newRegions, hiddenRanges, this._textModel.getLineCount()); + const foldedOrManualRanges = this._currentFoldedOrManualRanges(blockedLineNumers); + const newRanges = FoldingRegions.sanitizeAndMerge(newRegions, foldedOrManualRanges, this._textModel.getLineCount()); this.updatePost(FoldingRegions.fromFoldRanges(newRanges)); } @@ -111,14 +124,14 @@ export class FoldingModel { const startLineNumber = newRegions.getStartLineNumber(index); const endLineNumber = newRegions.getEndLineNumber(index); const isCollapsed = newRegions.isCollapsed(index); - const isManualSelection = newRegions.isManualSelection(index); + const isManual = newRegions.getSource(index) !== FoldSource.provider; const decorationRange = { startLineNumber: startLineNumber, startColumn: this._textModel.getLineMaxColumn(startLineNumber), endLineNumber: endLineNumber, endColumn: this._textModel.getLineMaxColumn(endLineNumber) + 1 }; - newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManualSelection) }); + newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManual) }); if (isCollapsed && endLineNumber > lastHiddenLine) { lastHiddenLine = endLineNumber; } @@ -128,7 +141,7 @@ export class FoldingModel { this._updateEventEmitter.fire({ model: this }); } - private _currentHiddenRegions(blockedLineNumers: number[] = []): FoldRange[] { + private _currentFoldedOrManualRanges(blockedLineNumers: number[] = []): FoldRange[] { const isBlocked = (startLineNumber: number, endLineNumber: number) => { for (const blockedLineNumber of blockedLineNumers) { @@ -139,42 +152,45 @@ export class FoldingModel { return false; }; - const hiddenRanges: FoldRange[] = []; + const foldedRanges: FoldRange[] = []; for (let i = 0, limit = this._regions.length; i < limit; i++) { - if (this.regions.isCollapsed(i)) { - const hiddenRange = this._regions.toFoldRange(i); + let isCollapsed = this.regions.isCollapsed(i); + const source = this.regions.getSource(i); + if (isCollapsed || source !== FoldSource.provider) { + const foldRange = this._regions.toFoldRange(i); const decRange = this._textModel.getDecorationRange(this._editorDecorationIds[i]); - if (decRange - && !isBlocked(decRange.startLineNumber, decRange.endLineNumber) - // if not same length user has modified it, skip and auto-expand - && decRange.endLineNumber - decRange.startLineNumber - === hiddenRange.endLineNumber - hiddenRange.startLineNumber) { - hiddenRanges.push({ + if (decRange) { + if (isCollapsed && (isBlocked(decRange.startLineNumber, decRange.endLineNumber) || decRange.endLineNumber - decRange.startLineNumber !== foldRange.endLineNumber - foldRange.startLineNumber)) { + isCollapsed = false; // uncollapse is the range is blocked or there has been lines removed or added + } + foldedRanges.push({ startLineNumber: decRange.startLineNumber, endLineNumber: decRange.endLineNumber, - type: hiddenRange.type, - isCollapsed: true, - isManualSelection: hiddenRange.isManualSelection + type: foldRange.type, + isCollapsed, + source }); } } } - return hiddenRanges; + return foldedRanges; } /** * Collapse state memento, for persistence only */ public getMemento(): CollapseMemento | undefined { - const hiddenRegions = this._currentHiddenRegions(); + const foldedOrManualRanges = this._currentFoldedOrManualRanges(); const result: ILineMemento[] = []; - for (let i = 0, limit = hiddenRegions.length; i < limit; i++) { - const range = hiddenRegions[i]; + for (let i = 0, limit = foldedOrManualRanges.length; i < limit; i++) { + const range = foldedOrManualRanges[i]; const checksum = this._getLinesChecksum(range.startLineNumber + 1, range.endLineNumber); result.push({ startLineNumber: range.startLineNumber, endLineNumber: range.endLineNumber, + isCollapsed: range.isCollapsed, + source: range.source, checksum: checksum }); } @@ -188,7 +204,7 @@ export class FoldingModel { if (!Array.isArray(state)) { return; } - const hiddenRanges: FoldRange[] = []; + const rangesToRestore: FoldRange[] = []; const maxLineNumber = this._textModel.getLineCount(); for (const range of state) { if (range.startLineNumber >= range.endLineNumber || range.startLineNumber < 1 || range.endLineNumber > maxLineNumber) { @@ -196,17 +212,17 @@ export class FoldingModel { } const checksum = this._getLinesChecksum(range.startLineNumber + 1, range.endLineNumber); if (!range.checksum || checksum === range.checksum) { - hiddenRanges.push({ + rangesToRestore.push({ startLineNumber: range.startLineNumber, endLineNumber: range.endLineNumber, type: undefined, - isCollapsed: true, - isManualSelection: true // converts to false when provider sends a match + isCollapsed: range.isCollapsed ?? true, + source: range.source ?? FoldSource.provider }); } } - const newRanges = FoldingRegions.sanitizeAndMerge(this._regions, hiddenRanges, maxLineNumber); + const newRanges = FoldingRegions.sanitizeAndMerge(this._regions, rangesToRestore, maxLineNumber); this.updatePost(FoldingRegions.fromFoldRanges(newRanges)); } diff --git a/src/vs/editor/contrib/folding/browser/foldingRanges.ts b/src/vs/editor/contrib/folding/browser/foldingRanges.ts index b45c3222349..8beb820778d 100644 --- a/src/vs/editor/contrib/folding/browser/foldingRanges.ts +++ b/src/vs/editor/contrib/folding/browser/foldingRanges.ts @@ -8,12 +8,18 @@ export interface ILineRange { endLineNumber: number; } +export const enum FoldSource { + provider = 0, + userDefined = 1, + recovered = 2 +} + export interface FoldRange { startLineNumber: number; endLineNumber: number; type: string | undefined; isCollapsed: boolean; - isManualSelection: boolean; + source: FoldSource; } export const MAX_FOLDING_REGIONS = 0xFFFF; @@ -21,11 +27,38 @@ export const MAX_LINE_NUMBER = 0xFFFFFF; const MASK_INDENT = 0xFF000000; +class BitField { + private readonly _states: Uint32Array; + constructor(size: number) { + const numWords = Math.ceil(size / 32); + this._states = new Uint32Array(numWords); + } + + public get(index: number): boolean { + const arrayIndex = (index / 32) | 0; + const bit = index % 32; + return (this._states[arrayIndex] & (1 << bit)) !== 0; + } + + public set(index: number, newState: boolean) { + const arrayIndex = (index / 32) | 0; + const bit = index % 32; + const value = this._states[arrayIndex]; + if (newState) { + this._states[arrayIndex] = value | (1 << bit); + } else { + this._states[arrayIndex] = value & ~(1 << bit); + } + } +} + export class FoldingRegions { private readonly _startIndexes: Uint32Array; private readonly _endIndexes: Uint32Array; - private readonly _collapseStates: Uint32Array; - private readonly _manualStates: Uint32Array; + private readonly _collapseStates: BitField; + private readonly _userDefinedStates: BitField; + private readonly _recoveredStates: BitField; + private _parentsComputed: boolean; private readonly _types: Array | undefined; @@ -35,9 +68,9 @@ export class FoldingRegions { } this._startIndexes = startIndexes; this._endIndexes = endIndexes; - const numWords = Math.ceil(startIndexes.length / 32); - this._collapseStates = new Uint32Array(numWords); - this._manualStates = new Uint32Array(numWords); + this._collapseStates = new BitField(startIndexes.length); + this._userDefinedStates = new BitField(startIndexes.length); + this._recoveredStates = new BitField(startIndexes.length); this._types = types; this._parentsComputed = false; } @@ -88,36 +121,48 @@ export class FoldingRegions { } public isCollapsed(index: number): boolean { - const arrayIndex = (index / 32) | 0; - const bit = index % 32; - return (this._collapseStates[arrayIndex] & (1 << bit)) !== 0; + return this._collapseStates.get(index); } public setCollapsed(index: number, newState: boolean) { - const arrayIndex = (index / 32) | 0; - const bit = index % 32; - const value = this._collapseStates[arrayIndex]; - if (newState) { - this._collapseStates[arrayIndex] = value | (1 << bit); - } else { - this._collapseStates[arrayIndex] = value & ~(1 << bit); + this._collapseStates.set(index, newState); + } + + private isUserDefined(index: number): boolean { + return this._userDefinedStates.get(index); + } + + private setUserDefined(index: number, newState: boolean) { + return this._userDefinedStates.set(index, newState); + } + + private isRecovered(index: number): boolean { + return this._recoveredStates.get(index); + } + + private setRecovered(index: number, newState: boolean) { + return this._recoveredStates.set(index, newState); + } + + public getSource(index: number): FoldSource { + if (this.isUserDefined(index)) { + return FoldSource.userDefined; + } else if (this.isRecovered(index)) { + return FoldSource.recovered; } + return FoldSource.provider; } - public isManualSelection(index: number): boolean { - const arrayIndex = (index / 32) | 0; - const bit = index % 32; - return (this._manualStates[arrayIndex] & (1 << bit)) !== 0; - } - - public setManualSelection(index: number, newState: boolean) { - const arrayIndex = (index / 32) | 0; - const bit = index % 32; - const value = this._manualStates[arrayIndex]; - if (newState) { - this._manualStates[arrayIndex] = value | (1 << bit); + public setSource(index: number, source: FoldSource): void { + if (source === FoldSource.userDefined) { + this.setUserDefined(index, true); + this.setRecovered(index, false); + } else if (source === FoldSource.recovered) { + this.setUserDefined(index, false); + this.setRecovered(index, true); } else { - this._manualStates[arrayIndex] = value & ~(1 << bit); + this.setUserDefined(index, false); + this.setRecovered(index, false); } } @@ -185,10 +230,16 @@ export class FoldingRegions { return -1; } + private readonly sourceAbbr = { + [FoldSource.provider]: ' ', + [FoldSource.userDefined]: 'u', + [FoldSource.recovered]: 'r', + }; + public toString() { const res: string[] = []; for (let i = 0; i < this.length; i++) { - res[i] = `[${this.isManualSelection(i) ? '*' : ' '}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`; + res[i] = `[${this.sourceAbbr[this.getSource(i)]}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`; } return res.join(', '); } @@ -199,7 +250,7 @@ export class FoldingRegions { endLineNumber: this._endIndexes[index] & MAX_LINE_NUMBER, type: this._types ? this._types[index] : undefined, isCollapsed: this.isCollapsed(index), - isManualSelection: this.isManualSelection(index) + source: this.getSource(index) }; } @@ -226,9 +277,7 @@ export class FoldingRegions { if (ranges[i].isCollapsed) { regions.setCollapsed(i, true); } - if (ranges[i].isManualSelection) { - regions.setManualSelection(i, true); - } + regions.setSource(i, ranges[i].source); } return regions; } @@ -237,11 +286,10 @@ export class FoldingRegions { * Two inputs, each a FoldingRegions or a FoldRange[], are merged. * Each input must be pre-sorted on startLineNumber. * The first list is assumed to always include all regions currently defined by range providers. - * The second list only contains hidden ranges. + * The second list only contains the previously collapsed and all manual ranges. + * If the line position matches, the range of the new range is taken, and the range is no longer manual * When an entry in one list overlaps an entry in the other, the second list's entry "wins" and - * overlapping entries in the first list are discarded. With one exception: when there is just - * one such second list entry and it is not manual it is discarded, on the assumption that - * user editing has resulted in the range no longer existing. + * overlapping entries in the first list are discarded. * Invalid entries are discarded. An entry is invalid if: * the start and end line numbers aren't a valid range of line numbers, * it is out of sequence or has the same start line as a preceding entry, @@ -252,18 +300,6 @@ export class FoldingRegions { rangesB: FoldingRegions | FoldRange[], maxLineNumber: number | undefined): FoldRange[] { maxLineNumber = maxLineNumber ?? Number.MAX_VALUE; - let result = this._trySanitizeAndMerge(1, rangesA, rangesB, maxLineNumber); - if (!result) { // try again, converting hidden ranges to manually selected - result = this._trySanitizeAndMerge(2, rangesA, rangesB, maxLineNumber); - } - return result!; - } - - private static _trySanitizeAndMerge( - passNumber: number, // it can take two passes to get this done - rangesA: FoldingRegions | FoldRange[], - rangesB: FoldingRegions | FoldRange[], - maxLineNumber: number): FoldRange[] | null { const getIndexedFunction = (r: FoldingRegions | FoldRange[], limit: number) => { return Array.isArray(r) @@ -281,41 +317,32 @@ export class FoldingRegions { let topStackedRange: FoldRange | undefined; let prevLineNumber = 0; const resultRanges: FoldRange[] = []; - let numberAutoExpand = 0; while (nextA || nextB) { let useRange: FoldRange | undefined = undefined; if (nextB && (!nextA || nextA.startLineNumber >= nextB.startLineNumber)) { - // nextB is next - if (nextA - && nextA.startLineNumber === nextB.startLineNumber - && nextA.endLineNumber === nextB.endLineNumber) { - // same range in both lists, merge the details - useRange = nextB; - useRange.isCollapsed = useRange.isCollapsed || nextA.isCollapsed; - // next line removes manual flag when range provider has matching range - useRange.isManualSelection = nextA.isManualSelection && nextB.isManualSelection; - if (!useRange.type) { - useRange.type = nextA.type; + if (nextA && nextA.startLineNumber === nextB.startLineNumber) { + if (nextB.source === FoldSource.userDefined) { + // a user defined range (possibly unfolded) + useRange = nextB; + } else { + // a previously folded range or a (possibly unfolded) recovered range + useRange = nextA; + useRange.isCollapsed = nextB.isCollapsed && nextA.endLineNumber === nextB.endLineNumber; + useRange.source = FoldSource.provider; } nextA = getA(++indexA); // not necessary, just for speed - } else if (nextB.isCollapsed && !nextB.isManualSelection && passNumber === 1) { - if (++numberAutoExpand > 1) { - // do second pass keeping these, assuming something like an unmatched /* - return null; - } - // skip nextB (auto expand) by not setting useRange, assuming it was edited - } else { // use nextB + } else { useRange = nextB; - if (useRange.isCollapsed) { - // doesn't match nextA, convert to a manual selection if it wasn't already - useRange.isManualSelection = true; + if (nextB.isCollapsed && nextB.source === FoldSource.provider) { + // a previously collapsed range + useRange.source = FoldSource.recovered; } } nextB = getB(++indexB); } else { - // nextA is next. The B set takes precedence and we sometimes need to look + // nextA is next. The user folded B set takes precedence and we sometimes need to look // ahead in it to check for an upcoming conflict. let scanIndex = indexB; let prescanB = nextB; @@ -324,8 +351,8 @@ export class FoldingRegions { useRange = nextA; break; // no conflict, use this nextA } - if (prescanB.endLineNumber > nextA!.endLineNumber - && (!prescanB.isCollapsed || prescanB.isManualSelection || passNumber === 2)) { + if (prescanB.source === FoldSource.userDefined && prescanB.endLineNumber > nextA!.endLineNumber) { + // we found a user folded range, it wins break; // without setting nextResult, so this nextA gets skipped } prescanB = getB(++scanIndex); diff --git a/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts index a1979da3c22..9f6a381afbd 100644 --- a/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { FoldingMarkers } from 'vs/editor/common/languages/languageConfiguration'; -import { MAX_FOLDING_REGIONS, FoldRange, FoldingRegions } from 'vs/editor/contrib/folding/browser/foldingRanges'; +import { MAX_FOLDING_REGIONS, FoldRange, FoldingRegions, FoldSource } from 'vs/editor/contrib/folding/browser/foldingRanges'; import { computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; @@ -16,20 +16,20 @@ const markers: FoldingMarkers = { suite('FoldingRanges', () => { - const foldRange = (from: number, to: number, collapsed: boolean | undefined = undefined, manual: boolean | undefined = undefined, type: string | undefined = undefined) => + const foldRange = (from: number, to: number, collapsed: boolean | undefined = undefined, source: FoldSource = FoldSource.provider, type: string | undefined = undefined) => { startLineNumber: from, endLineNumber: to, type: type, isCollapsed: collapsed || false, - isManualSelection: manual || false + source }; const assertEqualRanges = (range1: FoldRange, range2: FoldRange, msg: string) => { assert.strictEqual(range1.startLineNumber, range2.startLineNumber, msg + ' start'); assert.strictEqual(range1.endLineNumber, range2.endLineNumber, msg + ' end'); assert.strictEqual(range1.type, range2.type, msg + ' type'); assert.strictEqual(range1.isCollapsed, range2.isCollapsed, msg + ' collapsed'); - assert.strictEqual(range1.isManualSelection, range2.isManualSelection, msg + ' manual'); + assert.strictEqual(range1.source, range2.source, msg + ' source'); }; test('test max folding regions', () => { @@ -122,110 +122,88 @@ suite('FoldingRanges', () => { test('sanitizeAndMerge1', () => { const regionSet1: FoldRange[] = [ foldRange(0, 100), // invalid, should be removed - foldRange(1, 100, false, false, 'A'), // valid - foldRange(1, 100, false, false, 'Z'), // invalid, duplicate start + foldRange(1, 100, false, FoldSource.provider, 'A'), // valid + foldRange(1, 100, false, FoldSource.provider, 'Z'), // invalid, duplicate start foldRange(10, 10, false), // invalid, should be removed - foldRange(20, 80, false, false, 'C1'), // valid inside 'B' - foldRange(22, 80, true, false, 'D1'), // valid inside 'C1' + foldRange(20, 80, false, FoldSource.provider, 'C1'), // valid inside 'B' + foldRange(22, 80, true, FoldSource.provider, 'D1'), // valid inside 'C1' foldRange(90, 101), // invalid, should be removed ]; const regionSet2: FoldRange[] = [ - foldRange(2, 100, false, false, 'B'), // valid, inside 'A' foldRange(20, 80, true), // should merge with C1 foldRange(18, 80, true), // invalid, out of order - foldRange(21, 81, true, false, 'Z'), // invalid, overlapping - foldRange(22, 80, false, false, 'D2'), // should merge with D1 + foldRange(21, 81, true, FoldSource.provider, 'Z'), // invalid, overlapping + foldRange(22, 80, true, FoldSource.provider, 'D2'), // should merge with D1 ]; - let result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100); - assert.strictEqual(result.length, 4, 'result length1'); - assertEqualRanges(result[0], foldRange(1, 100, false, false, 'A'), 'A1'); - assertEqualRanges(result[1], foldRange(2, 100, false, false, 'B'), 'B1'); - assertEqualRanges(result[2], foldRange(20, 80, true, false, 'C1'), 'C1'); - assertEqualRanges(result[3], foldRange(22, 80, true, false, 'D2'), 'D1'); - const regionClass1 = FoldingRegions.fromFoldRanges(regionSet1); - const regionClass2 = FoldingRegions.fromFoldRanges(regionSet2); - // same tests again with inputs as FoldingRegions instead of FoldRange[] - result = FoldingRegions.sanitizeAndMerge(regionClass1, regionClass2, 100); - assert.strictEqual(result.length, 4, 'result length2'); - assertEqualRanges(result[0], foldRange(1, 100, false, false, 'A'), 'A2'); - assertEqualRanges(result[1], foldRange(2, 100, false, false, 'B'), 'B2'); - assertEqualRanges(result[2], foldRange(20, 80, true, false, 'C1'), 'C2'); - assertEqualRanges(result[3], foldRange(22, 80, true, false, 'D2'), 'D2'); + const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100); + assert.strictEqual(result.length, 3, 'result length1'); + assertEqualRanges(result[0], foldRange(1, 100, false, FoldSource.provider, 'A'), 'A1'); + assertEqualRanges(result[1], foldRange(20, 80, true, FoldSource.provider, 'C1'), 'C1'); + assertEqualRanges(result[2], foldRange(22, 80, true, FoldSource.provider, 'D1'), 'D1'); }); test('sanitizeAndMerge2', () => { const regionSet1: FoldRange[] = [ - foldRange(1, 100, false, false, 'a1'), // valid - foldRange(2, 100, false, false, 'a2'), // valid - foldRange(3, 19, false, false, 'a3'), // valid - foldRange(20, 71, false, false, 'a4'), // overlaps b3 - foldRange(21, 29, false, false, 'a5'), // valid - foldRange(81, 91, false, false, 'a6'), // overlaps b4 + foldRange(1, 100, false, FoldSource.provider, 'a1'), // valid + foldRange(2, 100, false, FoldSource.provider, 'a2'), // valid + foldRange(3, 19, false, FoldSource.provider, 'a3'), // valid + foldRange(20, 71, false, FoldSource.provider, 'a4'), // overlaps b3 + foldRange(21, 29, false, FoldSource.provider, 'a5'), // valid + foldRange(81, 91, false, FoldSource.provider, 'a6'), // overlaps b4 ]; const regionSet2: FoldRange[] = [ - foldRange(30, 39, false, false, 'b1'), // valid - foldRange(40, 49, false, false, 'b2'), // valid - foldRange(50, 100, false, false, 'b3'), // overlaps a4 - foldRange(80, 90, false, false, 'b4'), // overlaps a6 - foldRange(92, 100, false, false, 'b5'), // valid + foldRange(30, 39, true, FoldSource.provider, 'b1'), // valid, will be recovered + foldRange(40, 49, true, FoldSource.userDefined, 'b2'), // valid + foldRange(50, 100, true, FoldSource.userDefined, 'b3'), // overlaps a4 + foldRange(80, 90, true, FoldSource.userDefined, 'b4'), // overlaps a6 + foldRange(92, 100, true, FoldSource.userDefined, 'b5'), // valid ]; - let result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100); + const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100); assert.strictEqual(result.length, 9, 'result length1'); - assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'P1'); - assertEqualRanges(result[1], foldRange(2, 100, false, false, 'a2'), 'P2'); - assertEqualRanges(result[2], foldRange(3, 19, false, false, 'a3'), 'P3'); - assertEqualRanges(result[3], foldRange(21, 29, false, false, 'a5'), 'P4'); - assertEqualRanges(result[4], foldRange(30, 39, false, false, 'b1'), 'P5'); - assertEqualRanges(result[5], foldRange(40, 49, false, false, 'b2'), 'P6'); - assertEqualRanges(result[6], foldRange(50, 100, false, false, 'b3'), 'P7'); - assertEqualRanges(result[7], foldRange(80, 90, false, false, 'b4'), 'P8'); - assertEqualRanges(result[8], foldRange(92, 100, false, false, 'b5'), 'P9'); - // reverse the two inputs - result = FoldingRegions.sanitizeAndMerge(regionSet2, regionSet1, 100); - assert.strictEqual(result.length, 9, 'result length2'); - assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'Q1'); - assertEqualRanges(result[1], foldRange(2, 100, false, false, 'a2'), 'Q2'); - assertEqualRanges(result[2], foldRange(3, 19, false, false, 'a3'), 'Q3'); - assertEqualRanges(result[3], foldRange(20, 71, false, false, 'a4'), 'Q4'); - assertEqualRanges(result[4], foldRange(21, 29, false, false, 'a5'), 'Q5'); - assertEqualRanges(result[5], foldRange(30, 39, false, false, 'b1'), 'Q6'); - assertEqualRanges(result[6], foldRange(40, 49, false, false, 'b2'), 'Q7'); - assertEqualRanges(result[7], foldRange(81, 91, false, false, 'a6'), 'Q8'); - assertEqualRanges(result[8], foldRange(92, 100, false, false, 'b5'), 'Q9'); + assertEqualRanges(result[0], foldRange(1, 100, false, FoldSource.provider, 'a1'), 'P1'); + assertEqualRanges(result[1], foldRange(2, 100, false, FoldSource.provider, 'a2'), 'P2'); + assertEqualRanges(result[2], foldRange(3, 19, false, FoldSource.provider, 'a3'), 'P3'); + assertEqualRanges(result[3], foldRange(21, 29, false, FoldSource.provider, 'a5'), 'P4'); + assertEqualRanges(result[4], foldRange(30, 39, true, FoldSource.recovered, 'b1'), 'P5'); + assertEqualRanges(result[5], foldRange(40, 49, true, FoldSource.userDefined, 'b2'), 'P6'); + assertEqualRanges(result[6], foldRange(50, 100, true, FoldSource.userDefined, 'b3'), 'P7'); + assertEqualRanges(result[7], foldRange(80, 90, true, FoldSource.userDefined, 'b4'), 'P8'); + assertEqualRanges(result[8], foldRange(92, 100, true, FoldSource.userDefined, 'b5'), 'P9'); }); test('sanitizeAndMerge3', () => { const regionSet1: FoldRange[] = [ - foldRange(1, 100, false, false, 'a1'), // valid - foldRange(10, 29, false, false, 'a2'), // matches manual hidden - foldRange(35, 39, true, true, 'a3'), // valid + foldRange(1, 100, false, FoldSource.provider, 'a1'), // valid + foldRange(10, 29, false, FoldSource.provider, 'a2'), // matches manual hidden + foldRange(35, 39, true, FoldSource.recovered, 'a3'), // valid ]; const regionSet2: FoldRange[] = [ - foldRange(10, 29, true, true, 'b1'), // matches a - foldRange(20, 28, true, false, 'b2'), // should get dropped - foldRange(30, 39, true, true, 'b3'), // should remain + foldRange(10, 29, true, FoldSource.recovered, 'b1'), // matches a + foldRange(20, 28, true, FoldSource.provider, 'b2'), // should remain + foldRange(30, 39, true, FoldSource.recovered, 'b3'), // should remain ]; const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100); - assert.strictEqual(result.length, 4, 'result length3'); - assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'R1'); - assertEqualRanges(result[1], foldRange(10, 29, true, false, 'b1'), 'R2'); - assertEqualRanges(result[2], foldRange(30, 39, true, true, 'b3'), 'R3'); - assertEqualRanges(result[3], foldRange(35, 39, true, true, 'a3'), 'R4'); + assert.strictEqual(result.length, 5, 'result length3'); + assertEqualRanges(result[0], foldRange(1, 100, false, FoldSource.provider, 'a1'), 'R1'); + assertEqualRanges(result[1], foldRange(10, 29, true, FoldSource.provider, 'a2'), 'R2'); + assertEqualRanges(result[2], foldRange(20, 28, true, FoldSource.recovered, 'b2'), 'R3'); + assertEqualRanges(result[3], foldRange(30, 39, true, FoldSource.recovered, 'b3'), 'R3'); + assertEqualRanges(result[4], foldRange(35, 39, true, FoldSource.recovered, 'a3'), 'R4'); }); test('sanitizeAndMerge4', () => { const regionSet1: FoldRange[] = [ - foldRange(1, 100, false, false, 'a1'), // valid + foldRange(1, 100, false, FoldSource.provider, 'a1'), // valid ]; const regionSet2: FoldRange[] = [ - foldRange(20, 28, true, false, 'b1'), // hidden - foldRange(30, 38, true, false, 'b2'), // hidden + foldRange(20, 28, true, FoldSource.provider, 'b1'), // hidden + foldRange(30, 38, true, FoldSource.provider, 'b2'), // hidden ]; const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100); assert.strictEqual(result.length, 3, 'result length4'); - assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'R1'); - assertEqualRanges(result[1], foldRange(20, 28, true, true, 'b1'), 'R2'); - assertEqualRanges(result[2], foldRange(30, 38, true, true, 'b2'), 'R3'); + assertEqualRanges(result[0], foldRange(1, 100, false, FoldSource.provider, 'a1'), 'R1'); + assertEqualRanges(result[1], foldRange(20, 28, true, FoldSource.recovered, 'b1'), 'R2'); + assertEqualRanges(result[2], foldRange(30, 38, true, FoldSource.recovered, 'b2'), 'R3'); }); }); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index b60a6dec142..02104cfe890 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -699,7 +699,7 @@ export async function provideInlineCompletions( } for (const item of completions.items) { - const range = item.range ? Range.lift(item.range) : defaultReplaceRange; + let range = item.range ? Range.lift(item.range) : defaultReplaceRange; if (range.startLineNumber !== range.endLineNumber) { // Ignore invalid ranges. @@ -724,6 +724,12 @@ export async function provideInlineCompletions( model, languageConfigurationService ); + + // Modify range depending on if brackets are added or removed + const diff = insertText.length - item.insertText.length; + if (diff !== 0) { + range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn + diff); + } } snippetInfo = undefined; diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.ts new file mode 100644 index 00000000000..b394b835466 --- /dev/null +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.ts @@ -0,0 +1,408 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IActiveCodeEditor, ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; +import { CancellationToken, CancellationTokenSource, } from 'vs/base/common/cancellation'; +import * as dom from 'vs/base/browser/dom'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { createStringBuilder } from 'vs/editor/common/core/stringBuilder'; +import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { SymbolKind } from 'vs/editor/common/languages'; +import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; + +class StickyScrollController extends Disposable implements IEditorContribution { + + static readonly ID = 'store.contrib.stickyScrollController'; + private readonly _editor: ICodeEditor; + private readonly stickyScrollWidget: StickyScrollWidget; + private readonly _languageFeaturesService: ILanguageFeaturesService; + private readonly _sessionStore: DisposableStore = new DisposableStore(); + + private _ranges: [number, number, number][] = []; + private _rangesVersionId: number = 0; + private _cts: CancellationTokenSource | undefined; + private readonly _updateSoon: RunOnceScheduler; + + constructor( + editor: ICodeEditor, + @ILanguageFeaturesService _languageFeaturesService: ILanguageFeaturesService, + ) { + super(); + this._editor = editor; + this._languageFeaturesService = _languageFeaturesService; + this.stickyScrollWidget = new StickyScrollWidget(this._editor); + this._register(this._editor.onDidChangeConfiguration(e => { + if (e.hasChanged(EditorOption.experimental)) { + this.onConfigurationChange(); + } + })); + this._updateSoon = this._register(new RunOnceScheduler(() => this._update(true), 50)); + this.onConfigurationChange(); + } + + private onConfigurationChange() { + const options = this._editor.getOption(EditorOption.experimental); + if (options.stickyScroll.enabled === false) { + this.stickyScrollWidget.emptyRootNode(); + this._editor.removeOverlayWidget(this.stickyScrollWidget); + this._sessionStore.clear(); + return; + } else { + this._editor.addOverlayWidget(this.stickyScrollWidget); + this._sessionStore.add(this._editor.onDidChangeModel(() => this._update(true))); + this._sessionStore.add(this._editor.onDidScrollChange(() => this._update(false))); + this._sessionStore.add(this._editor.onDidChangeHiddenAreas(() => this._update(true))); + this._sessionStore.add(this._editor.onDidChangeModelTokens((e) => this._onTokensChange(e))); + this._sessionStore.add(this._editor.onDidChangeModelContent(() => this._updateSoon.schedule())); + this._sessionStore.add(this._languageFeaturesService.documentSymbolProvider.onDidChange(() => this._update(true))); + this._update(true); + } + } + + private _needsUpdate(event: IModelTokensChangedEvent) { + const stickyLineNumbers = this.stickyScrollWidget.getCurrentLines(); + for (const stickyLineNumber of stickyLineNumbers) { + for (const range of event.ranges) { + if (stickyLineNumber >= range.fromLineNumber && stickyLineNumber <= range.toLineNumber) { + return true; + } + } + } + return false; + } + + private _onTokensChange(event: IModelTokensChangedEvent) { + if (this._needsUpdate(event)) { + this._update(false); + } + } + + private async _update(updateOutline: boolean = false): Promise { + if (updateOutline) { + this._cts?.dispose(true); + this._cts = new CancellationTokenSource(); + await this._updateOutlineModel(this._cts.token); + } + const hiddenRanges: Range[] | undefined = this._editor._getViewModel()?.getHiddenAreas(); + if (hiddenRanges) { + for (const hiddenRange of hiddenRanges) { + this._ranges = this._ranges.filter(range => { return !(range[0] >= hiddenRange.startLineNumber && range[1] <= hiddenRange.endLineNumber + 1); }); + } + } + this._renderStickyScroll(); + } + + private _findLineRanges(outlineElement: OutlineElement, depth: number) { + if (outlineElement?.children.size) { + let didRecursion: boolean = false; + for (const outline of outlineElement?.children.values()) { + const kind: SymbolKind = outline.symbol.kind; + if (kind === SymbolKind.Class || kind === SymbolKind.Constructor || kind === SymbolKind.Function || kind === SymbolKind.Interface || kind === SymbolKind.Method || kind === SymbolKind.Module) { + didRecursion = true; + this._findLineRanges(outline, depth + 1); + } + } + if (!didRecursion) { + this._addOutlineRanges(outlineElement, depth); + } + } else { + this._addOutlineRanges(outlineElement, depth); + } + } + + private _addOutlineRanges(outlineElement: OutlineElement, depth: number) { + let currentStartLine: number | undefined = 0; + let currentEndLine: number | undefined = 0; + + while (outlineElement) { + const kind: SymbolKind = outlineElement.symbol.kind; + if (kind === SymbolKind.Class || kind === SymbolKind.Constructor || kind === SymbolKind.Function || kind === SymbolKind.Interface || kind === SymbolKind.Method || kind === SymbolKind.Module) { + currentStartLine = outlineElement?.symbol.range.startLineNumber as number; + currentEndLine = outlineElement?.symbol.range.endLineNumber as number; + this._ranges.push([currentStartLine, currentEndLine, depth]); + depth--; + } + if (outlineElement.parent instanceof OutlineElement) { + outlineElement = outlineElement.parent; + } else { + break; + } + } + } + + private async _updateOutlineModel(token: CancellationToken) { + if (this._editor.hasModel()) { + const model = this._editor.getModel(); + const modelVersionId = model.getVersionId(); + const outlineModel = await OutlineModel.create(this._languageFeaturesService.documentSymbolProvider, model, token); + if (token.isCancellationRequested) { + return; + } + this._ranges = []; + this._rangesVersionId = modelVersionId; + for (const outline of outlineModel.children.values()) { + if (outline instanceof OutlineElement) { + const kind: SymbolKind = outline.symbol.kind; + if (kind === SymbolKind.Class || kind === SymbolKind.Constructor || kind === SymbolKind.Function || kind === SymbolKind.Interface || kind === SymbolKind.Method || kind === SymbolKind.Module) { + this._findLineRanges(outline, 1); + } else { + this._findLineRanges(outline, 0); + } + } + this._ranges = this._ranges.sort(function (a, b) { + if (a[0] !== b[0]) { + return a[0] - b[0]; + } else if (a[1] !== b[1]) { + return b[1] - a[1]; + } else { + return a[2] - b[2]; + } + }); + let previous: number[] = []; + for (const [index, arr] of this._ranges.entries()) { + const [start, end, _depth] = arr; + if (previous[0] === start && previous[1] === end) { + this._ranges.splice(index, 1); + } else { + previous = arr; + } + } + } + } + } + + private _renderStickyScroll() { + if (!(this._editor.hasModel())) { + return; + } + const lineHeight: number = this._editor.getOption(EditorOption.lineHeight); + const model = this._editor.getModel(); + if (this._rangesVersionId !== model.getVersionId()) { + // Old _ranges not updated yet + return; + } + const scrollTop = this._editor.getScrollTop(); + + this.stickyScrollWidget.emptyRootNode(); + const beginningLinesConsidered: Set = new Set(); + + for (const [index, arr] of this._ranges.entries()) { + const [start, end, depth] = arr; + if (end - start > 0 && model.getLineContent(start) !== '') { + const topOfElementAtDepth = (depth - 1) * lineHeight; + const bottomOfElementAtDepth = depth * lineHeight; + + const bottomOfBeginningLine = this._editor.getBottomForLineNumber(start) - scrollTop; + const topOfEndLine = this._editor.getTopForLineNumber(end) - scrollTop; + const bottomOfEndLine = this._editor.getBottomForLineNumber(end) - scrollTop; + + if (!beginningLinesConsidered.has(start)) { + if (topOfElementAtDepth >= topOfEndLine - 1 && topOfElementAtDepth < bottomOfEndLine - 2) { + beginningLinesConsidered.add(start); + this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(start, depth, this._editor, -1, bottomOfEndLine - bottomOfElementAtDepth)); + break; + } + else if (bottomOfElementAtDepth > bottomOfBeginningLine && bottomOfElementAtDepth < bottomOfEndLine - 1) { + beginningLinesConsidered.add(start); + this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(start, depth, this._editor, 0, 0)); + } + } else { + this._ranges.splice(index, 1); + } + } + } + this.stickyScrollWidget.updateRootNode(); + } + + override dispose(): void { + super.dispose(); + this._sessionStore.dispose(); + } +} + +const _ttPolicy = window.trustedTypes?.createPolicy('stickyScrollViewLayer', { createHTML: value => value }); + +class StickyScrollCodeLine { + + public readonly effectiveLineHeight: number = 0; + + constructor(private readonly _lineNumber: number, private readonly _depth: number, private readonly _editor: IActiveCodeEditor, + private readonly _zIndex: number, private readonly _relativePosition: number) { + this.effectiveLineHeight = this._editor.getOption(EditorOption.lineHeight) + this._relativePosition; + } + + get lineNumber() { + return this._lineNumber; + } + + getDomNode() { + + const root: HTMLElement = document.createElement('div'); + const viewModel = this._editor._getViewModel(); + const viewLineNumber = viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(this._lineNumber, 1)).lineNumber; + const lineRenderingData = viewModel.getViewLineRenderingData(viewLineNumber); + + let actualInlineDecorations: LineDecoration[]; + try { + actualInlineDecorations = LineDecoration.filter(lineRenderingData.inlineDecorations, viewLineNumber, lineRenderingData.minColumn, lineRenderingData.maxColumn); + } catch (err) { + actualInlineDecorations = []; + } + + const renderLineInput: RenderLineInput = new RenderLineInput(true, true, lineRenderingData.content, lineRenderingData.continuesWithWrappedLine, + lineRenderingData.isBasicASCII, lineRenderingData.containsRTL, 0, lineRenderingData.tokens, actualInlineDecorations, lineRenderingData.tabSize, + lineRenderingData.startVisibleColumn, 1, 1, 1, 100, 'none', true, true, null); + + const sb = createStringBuilder(400); + renderViewLine(renderLineInput, sb); + + let newLine; + if (_ttPolicy) { + newLine = _ttPolicy.createHTML(sb.build() as string); + } else { + newLine = sb.build(); + } + + const lineHTMLNode = document.createElement('span'); + lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`; + lineHTMLNode.style.overflow = 'hidden'; + lineHTMLNode.style.whiteSpace = 'nowrap'; + lineHTMLNode.style.display = 'inline-block'; + lineHTMLNode.style.lineHeight = this._editor.getOption(EditorOption.lineHeight).toString() + 'px'; + lineHTMLNode.innerHTML = newLine as string; + + const lineNumberHTMLNode = document.createElement('span'); + lineNumberHTMLNode.style.width = this._editor.getLayoutInfo().contentLeft.toString() + 'px'; + lineNumberHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`; + lineNumberHTMLNode.style.color = 'var(--vscode-editorLineNumber-foreground)'; + lineNumberHTMLNode.style.display = 'inline-block'; + lineNumberHTMLNode.style.lineHeight = this._editor.getOption(EditorOption.lineHeight).toString() + 'px'; + + const innerLineNumberHTML = document.createElement('span'); + innerLineNumberHTML.innerText = this._lineNumber.toString(); + innerLineNumberHTML.style.paddingLeft = this._editor.getLayoutInfo().lineNumbersLeft.toString() + 'px'; + innerLineNumberHTML.style.width = this._editor.getLayoutInfo().lineNumbersWidth.toString() + 'px'; + innerLineNumberHTML.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`; + innerLineNumberHTML.style.textAlign = 'right'; + innerLineNumberHTML.style.float = 'left'; + innerLineNumberHTML.style.lineHeight = this._editor.getOption(EditorOption.lineHeight).toString() + 'px'; + lineNumberHTMLNode.appendChild(innerLineNumberHTML); + + root.onclick = e => { + e.stopPropagation(); + e.preventDefault(); + this._editor.revealPosition({ lineNumber: this._lineNumber - this._depth + 1, column: 1 }); + }; + + root.onmouseover = e => { + innerLineNumberHTML.style.background = `var(--vscode-editorStickyScrollHover-background)`; + lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScrollHover-background)`; + lineNumberHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScrollHover-background)`; + root.style.backgroundColor = `var(--vscode-editorStickyScrollHover-background)`; + innerLineNumberHTML.style.cursor = `pointer`; + lineHTMLNode.style.cursor = `pointer`; + root.style.cursor = `pointer`; + lineNumberHTMLNode.style.cursor = `pointer`; + }; + root.onmouseleave = e => { + innerLineNumberHTML.style.background = `var(--vscode-editorStickyScroll-background)`; + lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`; + lineNumberHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`; + root.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`; + }; + + this._editor.applyFontInfo(lineHTMLNode); + this._editor.applyFontInfo(innerLineNumberHTML); + + root.appendChild(lineNumberHTMLNode); + root.appendChild(lineHTMLNode); + + root.style.zIndex = this._zIndex.toString(); + root.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`; + root.style.overflow = 'hidden'; + root.style.whiteSpace = 'nowrap'; + root.style.width = '100%'; + root.style.lineHeight = this._editor.getOption(EditorOption.lineHeight).toString() + 'px'; + root.style.height = this._editor.getOption(EditorOption.lineHeight).toString() + 'px'; + + // Special case for last line of sticky scroll + if (this._relativePosition) { + root.style.position = 'relative'; + root.style.top = this._relativePosition + 'px'; + root.style.width = '100%'; + } + return root; + } +} + +class StickyScrollWidget implements IOverlayWidget { + + private readonly arrayOfCodeLines: StickyScrollCodeLine[] = []; + private readonly rootDomNode: HTMLElement = document.createElement('div'); + + constructor(public readonly _editor: ICodeEditor) { + this.rootDomNode = document.createElement('div'); + this.rootDomNode.style.width = '100%'; + this.rootDomNode.style.boxShadow = `var(--vscode-scrollbar-shadow) 0 6px 6px -6px`; + } + + get codeLineCount() { + return this.arrayOfCodeLines.length; + } + + getCurrentLines(): number[] { + const widgetLineRange: number[] = []; + for (const codeLine of this.arrayOfCodeLines) { + widgetLineRange.push(codeLine.lineNumber); + } + return widgetLineRange; + } + + pushCodeLine(codeLine: StickyScrollCodeLine) { + this.arrayOfCodeLines.push(codeLine); + } + + updateRootNode() { + let widgetHeight: number = 0; + for (const line of this.arrayOfCodeLines) { + widgetHeight += line.effectiveLineHeight; + this.rootDomNode.appendChild(line.getDomNode()); + } + this.rootDomNode.style.height = widgetHeight.toString() + 'px'; + } + + emptyRootNode() { + this.arrayOfCodeLines.length = 0; + dom.clearNode(this.rootDomNode); + } + + getId(): string { + return 'editor.contrib.stickyScrollWidget'; + } + + getDomNode(): HTMLElement { + this.rootDomNode.style.zIndex = '2'; + this.rootDomNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`; + return this.rootDomNode; + } + + getPosition(): IOverlayWidgetPosition | null { + return { + preference: null + }; + } +} + +registerEditorContribution(StickyScrollController.ID, StickyScrollController); + diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index a33146a005f..c32b55e0c07 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -41,6 +41,7 @@ import 'vs/editor/contrib/links/browser/links'; import 'vs/editor/contrib/multicursor/browser/multicursor'; import 'vs/editor/contrib/parameterHints/browser/parameterHints'; import 'vs/editor/contrib/rename/browser/rename'; +import 'vs/editor/contrib/stickyScroll/browser/stickyScroll'; import 'vs/editor/contrib/smartSelect/browser/smartSelect'; import 'vs/editor/contrib/snippet/browser/snippetController2'; import 'vs/editor/contrib/suggest/browser/suggestController'; diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index decc60790f5..56f84726c98 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -526,12 +526,13 @@ export function registerCodeActionProvider(languageSelector: LanguageSelector, p const languageFeaturesService = StandaloneServices.get(ILanguageFeaturesService); return languageFeaturesService.codeActionProvider.register(languageSelector, { providedCodeActionKinds: metadata?.providedCodeActionKinds, + documentation: metadata?.documentation, provideCodeActions: (model: model.ITextModel, range: Range, context: languages.CodeActionContext, token: CancellationToken): languages.ProviderResult => { const markerService = StandaloneServices.get(IMarkerService); const markers = markerService.read({ resource: model.uri }).filter(m => { return Range.areIntersectingOrTouching(m, range); }); - return provider.provideCodeActions(model, range, { markers, only: context.only }, token); + return provider.provideCodeActions(model, range, { markers, only: context.only, trigger: context.trigger }, token); }, resolveCodeAction: provider.resolveCodeAction }); @@ -664,6 +665,11 @@ export interface CodeActionContext { * Requested kind of actions to return. */ readonly only?: string; + + /** + * The reason why code actions were requested. + */ + readonly trigger: languages.CodeActionTriggerType; } /** @@ -697,6 +703,8 @@ export interface CodeActionProviderMetadata { * such as `["quickfix.removeLine", "source.fixAll" ...]`. */ readonly providedCodeActionKinds?: readonly string[]; + + readonly documentation?: ReadonlyArray<{ readonly kind: string; readonly command: languages.Command }>; } /** @@ -753,6 +761,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { SignatureHelpTriggerKind: standaloneEnums.SignatureHelpTriggerKind, InlayHintKind: standaloneEnums.InlayHintKind, InlineCompletionTriggerKind: standaloneEnums.InlineCompletionTriggerKind, + CodeActionTriggerType: standaloneEnums.CodeActionTriggerType, // classes FoldingRangeKind: languages.FoldingRangeKind, diff --git a/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts index 3c0942117e1..9a057ea0ec3 100644 --- a/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts +++ b/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts @@ -97,7 +97,7 @@ suite('ViewModelDecorations', () => { 'dec14', ]); - const inlineDecorations1 = viewModel.getViewLineRenderingData( + const inlineDecorations1 = viewModel.getViewportViewLineRenderingData( new Range(1, viewModel.getLineMinColumn(1), 2, viewModel.getLineMaxColumn(2)), 1 ).inlineDecorations; @@ -118,7 +118,7 @@ suite('ViewModelDecorations', () => { new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec5', InlineDecorationType.Before), ]); - const inlineDecorations2 = viewModel.getViewLineRenderingData( + const inlineDecorations2 = viewModel.getViewportViewLineRenderingData( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)), 2 ).inlineDecorations; @@ -148,7 +148,7 @@ suite('ViewModelDecorations', () => { new InlineDecoration(new Range(2, 3, 2, 3), 'b-dec12', InlineDecorationType.Before), ]); - const inlineDecorations3 = viewModel.getViewLineRenderingData( + const inlineDecorations3 = viewModel.getViewportViewLineRenderingData( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)), 3 ).inlineDecorations; @@ -198,13 +198,13 @@ suite('ViewModelDecorations', () => { ).filter(x => Boolean(x.options.beforeContentClassName)); assert.deepStrictEqual(decorations, []); - const inlineDecorations1 = viewModel.getViewLineRenderingData( + const inlineDecorations1 = viewModel.getViewportViewLineRenderingData( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)), 2 ).inlineDecorations; assert.deepStrictEqual(inlineDecorations1, []); - const inlineDecorations2 = viewModel.getViewLineRenderingData( + const inlineDecorations2 = viewModel.getViewportViewLineRenderingData( new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)), 3 ).inlineDecorations; @@ -229,7 +229,7 @@ suite('ViewModelDecorations', () => { ); }); - const inlineDecorations = viewModel.getViewLineRenderingData( + const inlineDecorations = viewModel.getViewportViewLineRenderingData( new Range(1, 1, 1, 1), 1 ).inlineDecorations; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index f608381f9e4..8b0f9bbc295 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1738,6 +1738,15 @@ declare namespace monaco.editor { GrowsOnlyWhenTypingAfter = 3 } + /** + * Text snapshot that works like an iterator. + * Will try to return chunks of roughly ~64KB size. + * Will return null when finished. + */ + export interface ITextSnapshot { + read(): string | null; + } + /** * A model. */ @@ -1769,7 +1778,7 @@ declare namespace monaco.editor { /** * Replace the entire text buffer value contained in this model. */ - setValue(newValue: string): void; + setValue(newValue: string | ITextSnapshot): void; /** * Get the text stored in this model. * @param eol The end of line character preference. Defaults to `EndOfLinePreference.TextDefined`. @@ -1777,6 +1786,12 @@ declare namespace monaco.editor { * @return The text. */ getValue(eol?: EndOfLinePreference, preserveBOM?: boolean): string; + /** + * Get the text stored in this model. + * @param preserverBOM Preserve a BOM character if it was detected when the model was constructed. + * @return The text snapshot (it is safe to consume it asynchronously). + */ + createSnapshot(preserveBOM?: boolean): ITextSnapshot; /** * Get the length of the text stored in this model. */ @@ -2931,6 +2946,10 @@ declare namespace monaco.editor { * Control the behavior and rendering of the scrollbars. */ scrollbar?: IEditorScrollbarOptions; + /** + * Control the behavior of experimental options + */ + experimental?: IEditorExperimentalOptions; /** * Control the behavior and rendering of the minimap. */ @@ -3420,11 +3439,11 @@ declare namespace monaco.editor { */ bracketPairColorization?: IBracketPairColorizationOptions; /** - * Enables dropping into the editor from an external source. + * Controls dropping into the editor from an external source. * - * This shows a preview of the drop location and triggers an `onDropIntoEditor` event. + * When enabled, this shows a preview of the drop location and triggers an `onDropIntoEditor` event. */ - enableDropIntoEditor?: boolean; + dropIntoEditor?: IDropIntoEditorOptions; } export interface IDiffEditorBaseOptions { @@ -3789,6 +3808,24 @@ declare namespace monaco.editor { enabled?: boolean; } + export interface IEditorExperimentalOptions { + /** + * Configuration options for editor sticky scroll + */ + stickyScroll?: { + /** + * Enable the sticky scroll + */ + enabled?: boolean; + }; + } + + export interface EditorExperimentalOptions { + stickyScroll: { + enabled: boolean; + }; + } + /** * Configuration options for editor inlayHints */ @@ -4301,6 +4338,17 @@ declare namespace monaco.editor { readonly wrappingColumn: number; } + /** + * Configuration options for editor drop into behavior + */ + export interface IDropIntoEditorOptions { + /** + * Enable the dropping into editor. + * Defaults to true. + */ + enabled?: boolean; + } + export enum EditorOption { acceptSuggestionOnCommitCharacter = 0, acceptSuggestionOnEnter = 1, @@ -4334,108 +4382,109 @@ declare namespace monaco.editor { disableMonospaceOptimizations = 29, domReadOnly = 30, dragAndDrop = 31, - enableDropIntoEditor = 32, + dropIntoEditor = 32, emptySelectionClipboard = 33, - extraEditorClassName = 34, - fastScrollSensitivity = 35, - find = 36, - fixedOverflowWidgets = 37, - folding = 38, - foldingStrategy = 39, - foldingHighlight = 40, - foldingImportsByDefault = 41, - foldingMaximumRegions = 42, - unfoldOnClickAfterEndOfLine = 43, - fontFamily = 44, - fontInfo = 45, - fontLigatures = 46, - fontSize = 47, - fontWeight = 48, - formatOnPaste = 49, - formatOnType = 50, - glyphMargin = 51, - gotoLocation = 52, - hideCursorInOverviewRuler = 53, - hover = 54, - inDiffEditor = 55, - inlineSuggest = 56, - letterSpacing = 57, - lightbulb = 58, - lineDecorationsWidth = 59, - lineHeight = 60, - lineNumbers = 61, - lineNumbersMinChars = 62, - linkedEditing = 63, - links = 64, - matchBrackets = 65, - minimap = 66, - mouseStyle = 67, - mouseWheelScrollSensitivity = 68, - mouseWheelZoom = 69, - multiCursorMergeOverlapping = 70, - multiCursorModifier = 71, - multiCursorPaste = 72, - occurrencesHighlight = 73, - overviewRulerBorder = 74, - overviewRulerLanes = 75, - padding = 76, - parameterHints = 77, - peekWidgetDefaultFocus = 78, - definitionLinkOpensInPeek = 79, - quickSuggestions = 80, - quickSuggestionsDelay = 81, - readOnly = 82, - renameOnType = 83, - renderControlCharacters = 84, - renderFinalNewline = 85, - renderLineHighlight = 86, - renderLineHighlightOnlyWhenFocus = 87, - renderValidationDecorations = 88, - renderWhitespace = 89, - revealHorizontalRightPadding = 90, - roundedSelection = 91, - rulers = 92, - scrollbar = 93, - scrollBeyondLastColumn = 94, - scrollBeyondLastLine = 95, - scrollPredominantAxis = 96, - selectionClipboard = 97, - selectionHighlight = 98, - selectOnLineNumbers = 99, - showFoldingControls = 100, - showUnused = 101, - snippetSuggestions = 102, - smartSelect = 103, - smoothScrolling = 104, - stickyTabStops = 105, - stopRenderingLineAfter = 106, - suggest = 107, - suggestFontSize = 108, - suggestLineHeight = 109, - suggestOnTriggerCharacters = 110, - suggestSelection = 111, - tabCompletion = 112, - tabIndex = 113, - unicodeHighlighting = 114, - unusualLineTerminators = 115, - useShadowDOM = 116, - useTabStops = 117, - wordSeparators = 118, - wordWrap = 119, - wordWrapBreakAfterCharacters = 120, - wordWrapBreakBeforeCharacters = 121, - wordWrapColumn = 122, - wordWrapOverride1 = 123, - wordWrapOverride2 = 124, - wrappingIndent = 125, - wrappingStrategy = 126, - showDeprecated = 127, - inlayHints = 128, - editorClassName = 129, - pixelRatio = 130, - tabFocusMode = 131, - layoutInfo = 132, - wrappingInfo = 133 + experimental = 34, + extraEditorClassName = 35, + fastScrollSensitivity = 36, + find = 37, + fixedOverflowWidgets = 38, + folding = 39, + foldingStrategy = 40, + foldingHighlight = 41, + foldingImportsByDefault = 42, + foldingMaximumRegions = 43, + unfoldOnClickAfterEndOfLine = 44, + fontFamily = 45, + fontInfo = 46, + fontLigatures = 47, + fontSize = 48, + fontWeight = 49, + formatOnPaste = 50, + formatOnType = 51, + glyphMargin = 52, + gotoLocation = 53, + hideCursorInOverviewRuler = 54, + hover = 55, + inDiffEditor = 56, + inlineSuggest = 57, + letterSpacing = 58, + lightbulb = 59, + lineDecorationsWidth = 60, + lineHeight = 61, + lineNumbers = 62, + lineNumbersMinChars = 63, + linkedEditing = 64, + links = 65, + matchBrackets = 66, + minimap = 67, + mouseStyle = 68, + mouseWheelScrollSensitivity = 69, + mouseWheelZoom = 70, + multiCursorMergeOverlapping = 71, + multiCursorModifier = 72, + multiCursorPaste = 73, + occurrencesHighlight = 74, + overviewRulerBorder = 75, + overviewRulerLanes = 76, + padding = 77, + parameterHints = 78, + peekWidgetDefaultFocus = 79, + definitionLinkOpensInPeek = 80, + quickSuggestions = 81, + quickSuggestionsDelay = 82, + readOnly = 83, + renameOnType = 84, + renderControlCharacters = 85, + renderFinalNewline = 86, + renderLineHighlight = 87, + renderLineHighlightOnlyWhenFocus = 88, + renderValidationDecorations = 89, + renderWhitespace = 90, + revealHorizontalRightPadding = 91, + roundedSelection = 92, + rulers = 93, + scrollbar = 94, + scrollBeyondLastColumn = 95, + scrollBeyondLastLine = 96, + scrollPredominantAxis = 97, + selectionClipboard = 98, + selectionHighlight = 99, + selectOnLineNumbers = 100, + showFoldingControls = 101, + showUnused = 102, + snippetSuggestions = 103, + smartSelect = 104, + smoothScrolling = 105, + stickyTabStops = 106, + stopRenderingLineAfter = 107, + suggest = 108, + suggestFontSize = 109, + suggestLineHeight = 110, + suggestOnTriggerCharacters = 111, + suggestSelection = 112, + tabCompletion = 113, + tabIndex = 114, + unicodeHighlighting = 115, + unusualLineTerminators = 116, + useShadowDOM = 117, + useTabStops = 118, + wordSeparators = 119, + wordWrap = 120, + wordWrapBreakAfterCharacters = 121, + wordWrapBreakBeforeCharacters = 122, + wordWrapColumn = 123, + wordWrapOverride1 = 124, + wordWrapOverride2 = 125, + wrappingIndent = 126, + wrappingStrategy = 127, + showDeprecated = 128, + inlayHints = 129, + editorClassName = 130, + pixelRatio = 131, + tabFocusMode = 132, + layoutInfo = 133, + wrappingInfo = 134 } export const EditorOptions: { @@ -4473,7 +4522,8 @@ declare namespace monaco.editor { domReadOnly: IEditorOption; dragAndDrop: IEditorOption; emptySelectionClipboard: IEditorOption; - enableDropIntoEditor: IEditorOption; + dropIntoEditor: IEditorOption>>; + experimental: IEditorOption; extraEditorClassName: IEditorOption; fastScrollSensitivity: IEditorOption; find: IEditorOption>>; @@ -5330,9 +5380,13 @@ declare namespace monaco.editor { */ getVisibleRanges(): Range[]; /** - * Get the vertical position (top offset) for the line w.r.t. to the first line. + * Get the vertical position (top offset) for the line's top w.r.t. to the first line. */ getTopForLineNumber(lineNumber: number): number; + /** + * Get the vertical position (top offset) for the line's bottom w.r.t. to the first line. + */ + getBottomForLineNumber(lineNumber: number): number; /** * Get the vertical position (top offset) for the position w.r.t. to the first line. */ @@ -5833,6 +5887,10 @@ declare namespace monaco.languages { * Requested kind of actions to return. */ readonly only?: string; + /** + * The reason why code actions were requested. + */ + readonly trigger: CodeActionTriggerType; } /** @@ -5863,6 +5921,10 @@ declare namespace monaco.languages { * such as `["quickfix.removeLine", "source.fixAll" ...]`. */ readonly providedCodeActionKinds?: readonly string[]; + readonly documentation?: ReadonlyArray<{ + readonly kind: string; + readonly command: Command; + }>; } /** @@ -6435,6 +6497,11 @@ declare namespace monaco.languages { disabled?: string; } + export enum CodeActionTriggerType { + Invoke = 1, + Auto = 2 + } + export interface CodeActionList extends IDisposable { readonly actions: ReadonlyArray; } @@ -6771,11 +6838,11 @@ declare namespace monaco.languages { provideDocumentSymbols(model: editor.ITextModel, token: CancellationToken): ProviderResult; } - export type TextEdit = { + export interface TextEdit { range: IRange; text: string; eol?: editor.EndOfLineSequence; - }; + } /** * Interface used to format a model diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts index b36b6ea904d..65b2cea1af6 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts @@ -223,8 +223,8 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer output.log(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id)); } - await this.extensionManagementService.installFromGallery(galleryExtension, { ...installOptions, installGivenVersion: !!version }); - output.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, galleryExtension.version)); + const local = await this.extensionManagementService.installFromGallery(galleryExtension, { ...installOptions, installGivenVersion: !!version }); + output.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, local.manifest.version)); return manifest; } catch (error) { if (isCancellationError(error)) { diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index b9ee570c43e..e5b6b8a1045 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -51,7 +51,7 @@ export interface ResourceLabelFormatter { } export interface ResourceLabelFormatting { - label: string | ((resource: URI) => string); // myLabel:/${path} + label: string; // myLabel:/${path} separator: '/' | '\\' | ''; tildify?: boolean; normalizeDriveLetter?: boolean; diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 9716ea6dc75..5af76e7958e 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -125,6 +125,7 @@ export const WorkbenchTreeElementHasParent = new RawContextKey('treeEle export const WorkbenchTreeElementCanExpand = new RawContextKey('treeElementCanExpand', false); export const WorkbenchTreeElementHasChild = new RawContextKey('treeElementHasChild', false); export const WorkbenchTreeFindOpen = new RawContextKey('treeFindOpen', false); +export const WorkbenchTreeSupportsFind = new RawContextKey('treeSupportsFind', true); const WorkbenchListTypeNavigationModeKey = 'listTypeNavigationMode'; /** @@ -189,7 +190,6 @@ class MultipleSelectionController extends Disposable implements IMultipleSele function toWorkbenchListOptions( accessor: ServicesAccessor, - container: HTMLElement, options: IListOptions, ): [IListOptions, IDisposable] { const configurationService = accessor.get(IConfigurationService); @@ -203,7 +203,7 @@ function toWorkbenchListOptions( mouseWheelScrollSensitivity: configurationService.getValue(mouseWheelScrollSensitivityKey), fastScrollSensitivity: configurationService.getValue(fastScrollSensitivityKey), multipleSelectionController: options.multipleSelectionController ?? disposables.add(new MultipleSelectionController(configurationService)), - keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService), + keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(keybindingService), }; return [result, disposables]; @@ -244,7 +244,7 @@ export class WorkbenchList extends List { @IInstantiationService instantiationService: IInstantiationService ) { const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey)); - const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, container, options); + const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options); super(user, container, delegate, renderers, { @@ -384,7 +384,7 @@ export class WorkbenchPagedList extends PagedList { @IInstantiationService instantiationService: IInstantiationService ) { const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey)); - const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, container, options); + const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options); super(user, container, delegate, renderers, { keyboardSupport: false, @@ -517,7 +517,7 @@ export class WorkbenchTable extends Table { @IInstantiationService instantiationService: IInstantiationService ) { const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey)); - const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, container, options); + const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options); super(user, container, delegate, columns, renderers, { @@ -812,7 +812,7 @@ class TreeResourceNavigator extends ResourceNavigator { } } -function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingService: IKeybindingService): IKeyboardNavigationEventFilter { +function createKeyboardNavigationEventFilter(keybindingService: IKeybindingService): IKeyboardNavigationEventFilter { let inChord = false; return event => { @@ -825,7 +825,7 @@ function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingS return false; } - const result = keybindingService.softDispatch(event, container); + const result = keybindingService.softDispatch(event, event.target); if (result?.enterChord) { inChord = true; @@ -862,7 +862,7 @@ export class WorkbenchObjectTree, TFilterData = void> @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService ) { - const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any); + const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any); super(user, container, delegate, renderers, treeOptions); this.disposables.add(disposable); this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService); @@ -903,7 +903,7 @@ export class WorkbenchCompressibleObjectTree, TFilter @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService ) { - const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any); + const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any); super(user, container, delegate, renderers, treeOptions); this.disposables.add(disposable); this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService); @@ -950,7 +950,7 @@ export class WorkbenchDataTree extends DataTree extends Async @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService ) { - const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any); + const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any); super(user, container, delegate, renderers, dataSource, treeOptions); this.disposables.add(disposable); this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService); @@ -1042,7 +1042,7 @@ export class WorkbenchCompressibleAsyncDataTree e @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService ) { - const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any); + const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any); super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions); this.disposables.add(disposable); this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService); @@ -1077,11 +1077,9 @@ function getDefaultTreeFindMode(configurationService: IConfigurationService) { function workbenchTreeDataPreamble | IAsyncDataTreeOptions>( accessor: ServicesAccessor, - container: HTMLElement, options: TOptions, ): { options: TOptions; getTypeNavigationMode: () => TypeNavigationMode | undefined; disposable: IDisposable } { const configurationService = accessor.get(IConfigurationService); - const keybindingService = accessor.get(IKeybindingService); const contextViewService = accessor.get(IContextViewService); const contextKeyService = accessor.get(IContextKeyService); const instantiationService = accessor.get(IInstantiationService); @@ -1107,7 +1105,7 @@ function workbenchTreeDataPreamble(treeExpandMode) === 'doubleClick'), @@ -1147,6 +1144,7 @@ class WorkbenchTreeInternals { private treeElementCanExpand: IContextKey; private treeElementHasChild: IContextKey; private treeFindOpen: IContextKey; + private treeSupportFindWidget: IContextKey; private _useAltAsMultipleSelectionModifier: boolean; private disposables: IDisposable[] = []; private styler: IDisposable | undefined; @@ -1180,7 +1178,10 @@ class WorkbenchTreeInternals { this.treeElementHasParent = WorkbenchTreeElementHasParent.bindTo(this.contextKeyService); this.treeElementCanExpand = WorkbenchTreeElementCanExpand.bindTo(this.contextKeyService); this.treeElementHasChild = WorkbenchTreeElementHasChild.bindTo(this.contextKeyService); + this.treeFindOpen = WorkbenchTreeFindOpen.bindTo(this.contextKeyService); + this.treeSupportFindWidget = WorkbenchTreeSupportsFind.bindTo(this.contextKeyService); + this.treeSupportFindWidget.set(options.findWidgetEnabled ?? true); this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); diff --git a/src/vs/platform/markers/common/markerService.ts b/src/vs/platform/markers/common/markerService.ts index e94d1f2bd74..6f6dd4e8810 100644 --- a/src/vs/platform/markers/common/markerService.ts +++ b/src/vs/platform/markers/common/markerService.ts @@ -103,7 +103,7 @@ class MarkerStats implements MarkerStatistics { const result: MarkerStatistics = { errors: 0, warnings: 0, infos: 0, unknowns: 0 }; // TODO this is a hack - if (resource.scheme === Schemas.inMemory || resource.scheme === Schemas.walkThrough || resource.scheme === Schemas.walkThroughSnippet) { + if (resource.scheme === Schemas.inMemory || resource.scheme === Schemas.walkThrough || resource.scheme === Schemas.walkThroughSnippet || resource.scheme === Schemas.vscodeSourceControl) { return result; } diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 2335efbde29..fde30c2d8a6 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -417,30 +417,48 @@ export interface IAddressProvider { } export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise { - try { - const reconnectionToken = generateUuid(); - const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); - const { protocol } = await doConnectRemoteAgentManagement(simpleOptions, CancellationToken.None); - return new ManagementPersistentConnection(options, remoteAuthority, clientId, reconnectionToken, protocol); - } catch (err) { - options.logService.error(`[remote-connection] An error occurred in the very first connect attempt, it will be treated as a permanent error! Error:`); - options.logService.error(err); - PersistentConnection.triggerPermanentFailure(0, 0, RemoteAuthorityResolverError.isHandled(err)); - throw err; - } + return createInitialConnection( + options, + async (simpleOptions) => { + const { protocol } = await doConnectRemoteAgentManagement(simpleOptions, CancellationToken.None); + return new ManagementPersistentConnection(options, remoteAuthority, clientId, simpleOptions.reconnectionToken, protocol); + } + ); } export async function connectRemoteAgentExtensionHost(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise { - try { - const reconnectionToken = generateUuid(); - const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); - const { protocol, debugPort } = await doConnectRemoteAgentExtensionHost(simpleOptions, startArguments, CancellationToken.None); - return new ExtensionHostPersistentConnection(options, startArguments, reconnectionToken, protocol, debugPort); - } catch (err) { - options.logService.error(`[remote-connection] An error occurred in the very first connect attempt, it will be treated as a permanent error! Error:`); - options.logService.error(err); - PersistentConnection.triggerPermanentFailure(0, 0, RemoteAuthorityResolverError.isHandled(err)); - throw err; + return createInitialConnection( + options, + async (simpleOptions) => { + const { protocol, debugPort } = await doConnectRemoteAgentExtensionHost(simpleOptions, startArguments, CancellationToken.None); + return new ExtensionHostPersistentConnection(options, startArguments, simpleOptions.reconnectionToken, protocol, debugPort); + } + ); +} + +/** + * Will attempt to connect 5 times. If it fails 5 consecutive times, it will give up. + */ +async function createInitialConnection(options: IConnectionOptions, connectionFactory: (simpleOptions: ISimpleConnectionOptions) => Promise): Promise { + const MAX_ATTEMPTS = 5; + + for (let attempt = 1; ; attempt++) { + try { + const reconnectionToken = generateUuid(); + const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); + const result = await connectionFactory(simpleOptions); + return result; + } catch (err) { + if (attempt < MAX_ATTEMPTS) { + options.logService.error(`[remote-connection][attempt ${attempt}] An error occurred in initial connection! Will retry... Error:`); + options.logService.error(err); + } else { + options.logService.error(`[remote-connection][attempt ${attempt}] An error occurred in initial connection! It will be treated as a permanent error. Error:`); + options.logService.error(err); + PersistentConnection.triggerPermanentFailure(0, 0, RemoteAuthorityResolverError.isHandled(err)); + throw err; + } + } } } diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 640526f89a9..fd568328255 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -9,7 +9,7 @@ import { parse as parseUrl } from 'url'; import { Promises } from 'vs/base/common/async'; import { streamToBufferReadableStream } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { canceled } from 'vs/base/common/errors'; +import { CancellationError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import * as streams from 'vs/base/common/stream'; import { isBoolean, isNumber } from 'vs/base/common/types'; @@ -170,7 +170,7 @@ export class RequestService extends Disposable implements IRequestService { token.onCancellationRequested(() => { req.abort(); - e(canceled()); + e(new CancellationError()); }); }); } diff --git a/src/vs/platform/telemetry/browser/1dsAppender.ts b/src/vs/platform/telemetry/browser/1dsAppender.ts index 5075fdfc7df..1481d2ffecd 100644 --- a/src/vs/platform/telemetry/browser/1dsAppender.ts +++ b/src/vs/platform/telemetry/browser/1dsAppender.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import type { AppInsightsCore } from '@microsoft/1ds-core-js'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AbstractOneDataSystemAppender } from 'vs/platform/telemetry/common/1dsAppender'; export class OneDataSystemWebAppender extends AbstractOneDataSystemAppender { constructor( - configurationService: IConfigurationService | undefined, + isInternalTelemetry: boolean, eventPrefix: string, defaultData: { [key: string]: any } | null, iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing ) { - super(configurationService, eventPrefix, defaultData, iKeyOrClientFactory); + super(isInternalTelemetry, eventPrefix, defaultData, iKeyOrClientFactory); // If we cannot fetch the endpoint it means it is down and we should not send any telemetry. // This is most likely due to ad blockers diff --git a/src/vs/platform/telemetry/common/1dsAppender.ts b/src/vs/platform/telemetry/common/1dsAppender.ts index 5dd4975c9e1..11c49cba163 100644 --- a/src/vs/platform/telemetry/common/1dsAppender.ts +++ b/src/vs/platform/telemetry/common/1dsAppender.ts @@ -7,7 +7,6 @@ import type { AppInsightsCore, IExtendedConfiguration } from '@microsoft/1ds-cor import type { IChannelConfiguration, IXHROverride, PostChannel } from '@microsoft/1ds-post-js'; import { onUnexpectedError } from 'vs/base/common/errors'; import { mixin } from 'vs/base/common/objects'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; const endpointUrl = 'https://mobile.events.data.microsoft.com/OneCollector/1.0'; @@ -63,7 +62,7 @@ export abstract class AbstractOneDataSystemAppender implements ITelemetryAppende protected readonly endPointUrl = endpointUrl; constructor( - private readonly _configurationService: IConfigurationService | undefined, + private readonly _isInternalTelemetry: boolean, private _eventPrefix: string, private _defaultData: { [key: string]: any } | null, iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing @@ -92,8 +91,7 @@ export abstract class AbstractOneDataSystemAppender implements ITelemetryAppende } if (!this._asyncAiCore) { - const isInternal = this._configurationService?.getValue('telemetry.internalTesting'); - this._asyncAiCore = getClient(this._aiCoreOrKey, isInternal, this._xhrOverride); + this._asyncAiCore = getClient(this._aiCoreOrKey, this._isInternalTelemetry, this._xhrOverride); } this._asyncAiCore.then( diff --git a/src/vs/platform/telemetry/common/commonProperties.ts b/src/vs/platform/telemetry/common/commonProperties.ts index 06bd318a5eb..1b1df77de55 100644 --- a/src/vs/platform/telemetry/common/commonProperties.ts +++ b/src/vs/platform/telemetry/common/commonProperties.ts @@ -25,7 +25,7 @@ export async function resolveCommonProperties( commit: string | undefined, version: string | undefined, machineId: string | undefined, - msftInternalDomains: string[] | undefined, + isInternalTelemetry: boolean, installSourcePath: string, product?: string ): Promise<{ [name: string]: string | boolean | undefined }> { @@ -50,10 +50,9 @@ export async function resolveCommonProperties( // __GDPR__COMMON__ "common.product" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } result['common.product'] = product || 'desktop'; - const msftInternal = verifyMicrosoftInternalDomain(msftInternalDomains || []); - if (msftInternal) { + if (isInternalTelemetry) { // __GDPR__COMMON__ "common.msftInternal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - result['common.msftInternal'] = msftInternal; + result['common.msftInternal'] = isInternalTelemetry; } // dynamic properties which value differs on each call diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index e8c5e417f2e..ded0380e412 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -11,6 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, ConfigurationTargetToString, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IProductService } from 'vs/platform/product/common/productService'; +import { verifyMicrosoftInternalDomain } from 'vs/platform/telemetry/common/commonProperties'; import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ICustomEndpointTelemetryService, ITelemetryData, ITelemetryEndpoint, ITelemetryInfo, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; @@ -252,6 +253,18 @@ function flatKeys(result: string[], prefix: string, value: { [key: string]: any } } +/** + * Whether or not this is an internal user + * @param productService The product service + * @param configService The config servivce + * @returns true if internal, false otherwise + */ +export function isInternalTelemetry(productService: IProductService, configService: IConfigurationService) { + const msftInternalDomains = productService.msftInternalDomains || []; + const internalTesting = configService.getValue('telemetry.internalTesting'); + return verifyMicrosoftInternalDomain(msftInternalDomains) || internalTesting; +} + interface IPathEnvironment { appRoot: string; extensionsPath: string; diff --git a/src/vs/platform/telemetry/node/1dsAppender.ts b/src/vs/platform/telemetry/node/1dsAppender.ts index c7b6442cf7a..b8825f6e8a9 100644 --- a/src/vs/platform/telemetry/node/1dsAppender.ts +++ b/src/vs/platform/telemetry/node/1dsAppender.ts @@ -6,14 +6,13 @@ import type { AppInsightsCore } from '@microsoft/1ds-core-js'; import type { IPayloadData, IXHROverride } from '@microsoft/1ds-post-js'; import * as https from 'https'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AbstractOneDataSystemAppender } from 'vs/platform/telemetry/common/1dsAppender'; export class OneDataSystemAppender extends AbstractOneDataSystemAppender { constructor( - configurationService: IConfigurationService | undefined, + isInternalTelemetry: boolean, eventPrefix: string, defaultData: { [key: string]: any } | null, iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing @@ -48,6 +47,6 @@ export class OneDataSystemAppender extends AbstractOneDataSystemAppender { } }; - super(configurationService, eventPrefix, defaultData, iKeyOrClientFactory, customHttpXHROverride); + super(isInternalTelemetry, eventPrefix, defaultData, iKeyOrClientFactory, customHttpXHROverride); } } diff --git a/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts b/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts index 0c0378845ab..c59461414d1 100644 --- a/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts +++ b/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts @@ -33,7 +33,7 @@ suite('AIAdapter', () => { setup(() => { appInsightsMock = new AppInsightsCoreMock(); - adapter = new OneDataSystemWebAppender(undefined, prefix, undefined!, () => appInsightsMock); + adapter = new OneDataSystemWebAppender(false, prefix, undefined!, () => appInsightsMock); }); teardown(() => { @@ -48,7 +48,7 @@ suite('AIAdapter', () => { }); test('addional data', () => { - adapter = new OneDataSystemWebAppender(undefined, prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock); + adapter = new OneDataSystemWebAppender(false, prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock); adapter.log('testEvent'); assert.strictEqual(appInsightsMock.events.length, 1); diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 4c3c72a1f30..3f56c931175 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -169,8 +169,14 @@ export interface IPtyHostAttachTarget { environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined; reconnectionOwner?: string; task?: { label: string; id: string; lastTask: string; group?: string }; + waitOnExit?: WaitOnExitValue; + hideFromUser?: boolean; + isFeatureTerminal?: boolean; + type?: TerminalType; } +export type TerminalType = 'Task' | 'Local' | undefined; + export enum TitleEventSource { /** From the API or the rename command that overrides any other type */ Api, @@ -446,7 +452,7 @@ export interface IShellLaunchConfig { reconnectionOwner?: string; /** Whether to wait for a key press before closing the terminal. */ - waitOnExit?: boolean | string | ((exitCode: number) => string); + waitOnExit?: WaitOnExitValue; /** * A string including ANSI escape sequences that will be written to the terminal emulator @@ -469,7 +475,9 @@ export interface IShellLaunchConfig { /** * This is a terminal that attaches to an already running terminal. */ - attachPersistentProcess?: { id: number; findRevivedId?: boolean; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections; reconnectionOwner?: string; task?: { label: string; id: string; lastTask: string; group?: string } }; + attachPersistentProcess?: { + id: number; findRevivedId?: boolean; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections; reconnectionOwner?: string; task?: { label: string; id: string; lastTask: string; group?: string }; type?: TerminalType; waitOnExit?: WaitOnExitValue; hideFromUser?: boolean; isFeatureTerminal?: boolean; + }; /** * Whether the terminal process environment should be exactly as provided in @@ -544,9 +552,11 @@ export interface IShellLaunchConfig { /** * The task associated with this terminal */ - task?: { lastTask: string; group?: string; label: string; id: string }; + task?: { label: string; id: string; lastTask: string; group?: string }; } +export type WaitOnExitValue = boolean | string | ((exitCode: number) => string); + export interface ICreateContributedTerminalProfileOptions { icon?: URI | string | { light: URI; dark: URI }; color?: string; diff --git a/src/vs/platform/terminal/common/terminalProcess.ts b/src/vs/platform/terminal/common/terminalProcess.ts index 4329c9e0635..dc360427a6b 100644 --- a/src/vs/platform/terminal/common/terminalProcess.ts +++ b/src/vs/platform/terminal/common/terminalProcess.ts @@ -5,7 +5,7 @@ import { UriComponents } from 'vs/base/common/uri'; import { ISerializableEnvironmentVariableCollection, ISerializableEnvironmentVariableCollections } from 'vs/platform/terminal/common/environmentVariable'; -import { IFixedTerminalDimensions, IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { IFixedTerminalDimensions, IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById, TerminalIcon, TerminalType, TitleEventSource, WaitOnExitValue } from 'vs/platform/terminal/common/terminal'; export interface ISingleTerminalConfiguration { userValue: T | undefined; @@ -62,6 +62,10 @@ export interface IProcessDetails { environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined; reconnectionOwner?: string; task?: { label: string; id: string; lastTask: string; group?: string }; + waitOnExit?: WaitOnExitValue; + hideFromUser?: boolean; + isFeatureTerminal?: boolean; + type?: TerminalType; } export type ITerminalTabLayoutInfoDto = IRawTerminalTabLayoutInfo; diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 2b7ad270732..5e1feff3086 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -411,7 +411,11 @@ export class PtyService extends Disposable implements IPtyService { fixedDimensions: persistentProcess.fixedDimensions, environmentVariableCollections: persistentProcess.processLaunchOptions.options.environmentVariableCollections, reconnectionOwner: persistentProcess.shellLaunchConfig.reconnectionOwner, - task: persistentProcess.shellLaunchConfig.task + task: persistentProcess.shellLaunchConfig.task, + waitOnExit: persistentProcess.shellLaunchConfig.waitOnExit, + hideFromUser: persistentProcess.shellLaunchConfig.hideFromUser, + isFeatureTerminal: persistentProcess.shellLaunchConfig.isFeatureTerminal, + type: persistentProcess.shellLaunchConfig.type }; } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 3305267657d..c1f226c3601 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -312,14 +312,18 @@ export const editorBackground = registerColor('editor.background', { light: '#ff */ export const editorForeground = registerColor('editor.foreground', { light: '#333333', dark: '#BBBBBB', hcDark: Color.white, hcLight: foreground }, nls.localize('editorForeground', "Editor default foreground color.")); +/** + * Sticky scroll + */ +export const editorStickyScrollBackground = registerColor('editorStickyScroll.background', { light: editorBackground, dark: editorBackground, hcDark: editorBackground, hcLight: editorBackground }, nls.localize('editorStickyScrollBackground', "Sticky scroll background color for the editor")); +export const editorStickyScrollHoverBackground = registerColor('editorStickyScrollHover.background', { dark: '#2A2D2E', light: '#F0F0F0', hcDark: null, hcLight: Color.fromHex('#0F4A85').transparent(0.1) }, nls.localize('editorStickyScrollHoverBackground', "Sticky scroll on hover background color for the editor")); + /** * Editor widgets */ export const editorWidgetBackground = registerColor('editorWidget.background', { dark: '#252526', light: '#F3F3F3', hcDark: '#0C141F', hcLight: Color.white }, nls.localize('editorWidgetBackground', 'Background color of editor widgets, such as find/replace.')); export const editorWidgetForeground = registerColor('editorWidget.foreground', { dark: foreground, light: foreground, hcDark: foreground, hcLight: foreground }, nls.localize('editorWidgetForeground', 'Foreground color of editor widgets, such as find/replace.')); - export const editorWidgetBorder = registerColor('editorWidget.border', { dark: '#454545', light: '#C8C8C8', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('editorWidgetBorder', 'Border color of editor widgets. The color is only used if the widget chooses to have a border and if the color is not overridden by a widget.')); - export const editorWidgetResizeBorder = registerColor('editorWidget.resizeBorder', { light: null, dark: null, hcDark: null, hcLight: null }, nls.localize('editorWidgetResizeBorder', "Border color of the resize bar of editor widgets. The color is only used if the widget chooses to have a resize border and if the color is not overridden by a widget.")); /** diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index 3d0c1ba1b4c..1d7ad908f95 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -155,7 +155,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf readonly _serviceBrand: undefined; - private enabled: boolean = false; + protected enabled: boolean = false; protected readonly defaultProfileShouldIncludeExtensionsResourceAlways: boolean = false; readonly profilesHome: URI; diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts index 0ac191f058a..db97f74bd82 100644 --- a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts @@ -17,6 +17,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; export const IUserDataProfilesMainService = refineServiceDecorator(IUserDataProfilesService); export interface IUserDataProfilesMainService extends IUserDataProfilesService { + isEnabled(): boolean; unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise; readonly onWillCreateProfile: Event; readonly onWillRemoveProfile: Event; @@ -34,6 +35,10 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme super(stateMainService, uriIdentityService, environmentService, fileService, logService); } + isEnabled(): boolean { + return this.enabled; + } + protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles); } diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index fce751e1f2d..938c6eb7300 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -305,14 +305,33 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.hookWindowMessage(WM_INITMENU, () => { const [x, y] = this._win.getPosition(); const cursorPos = screen.getCursorScreenPoint(); + const cx = cursorPos.x - x; + const cy = cursorPos.y - y; - // This is necessary to make sure the native system context menu does not show up. - this._win.setEnabled(false); - this._win.setEnabled(true); + // In some cases, show the default system context menu + // 1) The mouse position is not within the title bar + // 2) The mouse position is within the title bar, but over the app icon + // We do not know the exact title bar height but we make an estimate based on window height + const shouldTriggerDefaultSystemContextMenu = () => { + // Use the custom context menu when over the title bar, but not over the app icon + // The app icon is estimated to be 30px wide + // The title bar is estimated to be the max of 35px and 15% of the window height + if (cx > 30 && cy >= 0 && cy <= Math.max(this._win.getBounds().height * 0.15, 35)) { + return false; + } - this._onDidTriggerSystemContextMenu.fire({ x: cursorPos.x - x, y: cursorPos.y - y }); + return true; + }; - return 0; // skip native menu + if (!shouldTriggerDefaultSystemContextMenu()) { + // This is necessary to make sure the native system context menu does not show up. + this._win.setEnabled(false); + this._win.setEnabled(true); + + this._onDidTriggerSystemContextMenu.fire({ x: cx, y: cy }); + } + + return 0; }); } diff --git a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts index 2ee6816721e..8e5d39ff29e 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts @@ -205,8 +205,10 @@ export class WorkspacesManagementMainService extends Disposable implements IWork // Delete from disk this.doDeleteUntitledWorkspaceSync(workspace); - // unset workspace from profiles - this.userDataProfilesMainService.unsetWorkspace(workspace); + if (this.userDataProfilesMainService.isEnabled()) { + // unset workspace from profiles + this.userDataProfilesMainService.unsetWorkspace(workspace); + } // Event this._onDidDeleteUntitledWorkspace.fire(workspace); diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 4c5ec7e1e2a..837b0b500d4 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -44,7 +44,6 @@ const isSupportedForCmd = (optionId: keyof RemoteParsedArgs) => { case 'enable-smoke-test-driver': case 'extensions-download-dir': case 'builtin-extensions-dir': - case 'locate-shell-integration-path': case 'telemetry': return false; default: @@ -74,6 +73,7 @@ const isSupportedForPipe = (optionId: keyof RemoteParsedArgs) => { case 'category': case 'verbose': case 'remote': + case 'locate-shell-integration-path': return true; default: return false; @@ -135,6 +135,20 @@ export function main(desc: ProductDescription, args: string[]): void { console.log(buildVersionMessage(desc.version, desc.commit)); return; } + if (parsedArgs['locate-shell-integration-path']) { + let file: string; + switch (parsedArgs['locate-shell-integration-path']) { + // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path bash)"` + case 'bash': file = 'shellIntegration-bash.sh'; break; + // Usage: `if ($env:TERM_PROGRAM -eq "vscode") { . "$(code --locate-shell-integration-path pwsh)" }` + case 'pwsh': file = 'shellIntegration.ps1'; break; + // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"` + case 'zsh': file = 'shellIntegration-rc.zsh'; break; + default: throw new Error('Error using --locate-shell-integration-path: Invalid shell type'); + } + console.log(resolve(__dirname, '../..', 'workbench', 'contrib', 'terminal', 'browser', 'media', file)); + return; + } if (cliPipe) { if (parsedArgs['openExternal']) { openInBrowser(parsedArgs['_'], verbose); @@ -218,7 +232,6 @@ export function main(desc: ProductDescription, args: string[]): void { return; } - const newCommandline: string[] = []; for (const key in parsedArgs) { const val = parsedArgs[key as keyof typeof parsedArgs]; diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index ab5f5cb7d57..bc2d71f8f03 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -49,7 +49,7 @@ import { RequestService } from 'vs/platform/request/node/requestService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; -import { getPiiPathsFromEnvironment, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { getPiiPathsFromEnvironment, isInternalTelemetry, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry'; import { IPtyService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; @@ -131,15 +131,16 @@ export async function setupServerServices(connectionToken: ServerConnectionToken let oneDsAppender: ITelemetryAppender = NullAppender; const machineId = await getMachineId(); + const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { if (productService.aiConfig && productService.aiConfig.ariaKey) { - oneDsAppender = new OneDataSystemAppender(configurationService, eventPrefix, null, productService.aiConfig.ariaKey); + oneDsAppender = new OneDataSystemAppender(isInternal, eventPrefix, null, productService.aiConfig.ariaKey); disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data } const config: ITelemetryServiceConfig = { appenders: [oneDsAppender], - commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, productService.msftInternalDomains, environmentService.installSourcePath, 'remoteAgent'), + commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, isInternal, environmentService.installSourcePath, 'remoteAgent'), piiPaths: getPiiPathsFromEnvironment(environmentService) }; const initialTelemetryLevelArg = environmentService.args['telemetry-level']; diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 150c192a1b8..519e716b8c7 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -81,7 +81,7 @@ export class ToggleActivityBarVisibilityAction extends Action2 { id: ToggleActivityBarVisibilityAction.ID, title: { value: localize('toggleActivityBar', "Toggle Activity Bar Visibility"), - mnemonicTitle: localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar"), + mnemonicTitle: localize({ key: 'miActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Activity Bar"), original: 'Toggle Activity Bar Visibility' }, category: CATEGORIES.View, @@ -406,7 +406,7 @@ MenuRegistry.appendMenuItems([ group: '2_workbench_layout', command: { id: ToggleSidebarVisibilityAction.ID, - title: localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Primary Side Bar"), + title: localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "&&Primary Side Bar"), toggled: SideBarVisibleContext }, order: 1 @@ -417,7 +417,7 @@ MenuRegistry.appendMenuItems([ group: '0_workbench_layout', command: { id: ToggleSidebarVisibilityAction.ID, - title: localize('miShowSidebarNoMnnemonic', "Show Primary Side Bar"), + title: localize('miSidebarNoMnnemonic', "Primary Side Bar"), toggled: SideBarVisibleContext }, order: 0 @@ -464,7 +464,7 @@ export class ToggleStatusbarVisibilityAction extends Action2 { id: ToggleStatusbarVisibilityAction.ID, title: { value: localize('toggleStatusbar', "Toggle Status Bar Visibility"), - mnemonicTitle: localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "Show S&&tatus Bar"), + mnemonicTitle: localize({ key: 'miStatusbar', comment: ['&& denotes a mnemonic'] }, "S&&tatus Bar"), original: 'Toggle Status Bar Visibility' }, category: CATEGORIES.View, @@ -573,7 +573,7 @@ if (isWindows || isLinux || isWeb) { id: 'workbench.action.toggleMenuBar', title: { value: localize('toggleMenuBar', "Toggle Menu Bar"), - mnemonicTitle: localize({ key: 'miShowMenuBar', comment: ['&& denotes a mnemonic'] }, "Show Menu &&Bar"), + mnemonicTitle: localize({ key: 'miMenuBar', comment: ['&& denotes a mnemonic'] }, "Menu &&Bar"), original: 'Toggle Menu Bar' }, category: CATEGORIES.View, @@ -596,7 +596,7 @@ if (isWindows || isLinux || isWeb) { MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: 'workbench.action.toggleMenuBar', - title: localize('miShowMenuBarNoMnemonic', "Show Menu Bar"), + title: localize('miMenuBarNoMnemonic', "Menu Bar"), toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) }, order: 0 diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index ee4848ce1ae..f6d73f19284 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -7,7 +7,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen } from 'vs/platform/list/browser/listService'; +import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen, WorkbenchTreeSupportsFind } from 'vs/platform/list/browser/listService'; import { PagedList } from 'vs/base/browser/ui/list/listPaging'; import { equals, range } from 'vs/base/common/arrays'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -634,7 +634,7 @@ CommandsRegistry.registerCommandAlias('list.toggleFilterOnType', 'list.toggleFin KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.find', weight: KeybindingWeight.WorkbenchContrib, - when: RawWorkbenchListFocusContextKey, + when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchTreeSupportsFind), primary: KeyMod.CtrlCmd | KeyCode.KeyF, secondary: [KeyCode.F3], handler: (accessor) => { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index d5f339775a3..9151c945a6c 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -578,7 +578,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi input2: { resource: filesToMerge[1].resource }, base: { resource: filesToMerge[2].resource }, result: { resource: filesToMerge[3].resource }, - options: { pinned: true } + options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready }]; } diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index 2b0b723d0e3..cf521c32e16 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -76,7 +76,7 @@ MenuRegistry.appendMenuItems([ group: '0_workbench_layout', command: { id: ToggleAuxiliaryBarAction.ID, - title: localize('miShowAuxiliaryBarNoMnemonic', "Show Secondary Side Bar"), + title: localize('miAuxiliaryBarNoMnemonic', "Secondary Side Bar"), toggled: AuxiliaryBarVisibleContext }, order: 2 @@ -116,7 +116,7 @@ MenuRegistry.appendMenuItems([ group: '2_workbench_layout', command: { id: ToggleAuxiliaryBarAction.ID, - title: localize({ key: 'miShowAuxiliaryBar', comment: ['&& denotes a mnemonic'] }, "Show Secondary Si&&de Bar"), + title: localize({ key: 'miAuxiliaryBar', comment: ['&& denotes a mnemonic'] }, "Secondary Si&&de Bar"), toggled: AuxiliaryBarVisibleContext }, order: 2 diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 51879dbf0ba..0ef796ae3fa 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -504,7 +504,7 @@ registerAction2(class ToggleBreadcrumb extends Action2 { id: 'breadcrumbs.toggle', title: { value: localize('cmd.toggle', "Toggle Breadcrumbs"), - mnemonicTitle: localize('miShowBreadcrumbs', "Show &&Breadcrumbs"), + mnemonicTitle: localize('miBreadcrumbs', "&&Breadcrumbs"), original: 'Toggle Breadcrumbs', }, category: CATEGORIES.View, diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index e9686439360..9e46f90b593 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -32,7 +32,7 @@ interface IDropOperation { } function isDropIntoEditorEnabledGlobally(configurationService: IConfigurationService) { - return configurationService.getValue('workbench.editor.dropIntoEditor.enabled'); + return configurationService.getValue('editor.dropIntoEditor.enabled'); } function isDragIntoEditorEvent(e: DragEvent): boolean { @@ -597,11 +597,6 @@ export class EditorDropTarget extends Themable { private registerListeners(): void { this._register(addDisposableListener(this.container, EventType.DRAG_ENTER, e => this.onDragEnter(e))); this._register(addDisposableListener(this.container, EventType.DRAG_LEAVE, () => this.onDragLeave())); - this._register(addDisposableListener(this.container, EventType.DRAG_OVER, e => { - if (!this.overlay) { - this.onDragEnter(e); - } - })); [this.container, window].forEach(node => this._register(addDisposableListener(node as HTMLElement, EventType.DRAG_END, () => this.onDragEnd()))); } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index facf063df26..44e9ed15b06 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1154,9 +1154,9 @@ export class TabsTitleControl extends TitleControl { tabContainer.classList.toggle(`sticky-${option}`, isTabSticky && options.pinnedTabSizing === option); } - // Sticky compact/shrink tabs need a position to remain at their location + // If not wrapping tabs, sticky compact/shrink tabs need a position to remain at their location // when scrolling to stay in view (requirement for position: sticky) - if (isTabSticky && options.pinnedTabSizing !== 'normal') { + if (!options.wrapTabs && isTabSticky && options.pinnedTabSizing !== 'normal') { let stickyTabWidth = 0; switch (options.pinnedTabSizing) { case 'compact': diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 2aa1285f9a1..a638504849a 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -240,7 +240,6 @@ export class TextDiffEditor extends AbstractTextEditor imp return { ...super.getConfigurationOverrides(), readOnly, - enableDropIntoEditor: !readOnly, originalEditable: this.input instanceof DiffEditorInput && !this.input.original.hasCapability(EditorInputCapabilities.Readonly), lineDecorationsWidth: '2ch' }; @@ -251,7 +250,6 @@ export class TextDiffEditor extends AbstractTextEditor imp this.diffEditorControl?.updateOptions({ readOnly: input.hasCapability(EditorInputCapabilities.Readonly), originalEditable: !input.original.hasCapability(EditorInputCapabilities.Readonly), - enableDropIntoEditor: !input.hasCapability(EditorInputCapabilities.Readonly) }); } else { super.updateReadonly(input); diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index c803409ef74..da518cdc0e7 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -135,11 +135,7 @@ export abstract class AbstractTextEditor extends Abs protected updateReadonly(input: EditorInput): void { const readOnly = input.hasCapability(EditorInputCapabilities.Readonly); - - this.updateEditorControlOptions({ - readOnly, - enableDropIntoEditor: !readOnly - }); + this.updateEditorControlOptions({ readOnly }); } protected getConfigurationOverrides(): ICodeEditorOptions { @@ -150,7 +146,6 @@ export abstract class AbstractTextEditor extends Abs lineNumbersMinChars: 3, fixedOverflowWidgets: true, readOnly, - enableDropIntoEditor: !readOnly, renderValidationDecorations: 'on' // render problems even in readonly editors (https://github.com/microsoft/vscode/issues/89057) }; } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index e64439239c5..b23e8bd6fa3 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -422,7 +422,7 @@ MenuRegistry.appendMenuItems([ group: '2_workbench_layout', command: { id: TogglePanelAction.ID, - title: localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"), + title: localize({ key: 'miPanel', comment: ['&& denotes a mnemonic'] }, "&&Panel"), toggled: PanelVisibleContext }, order: 5 @@ -433,7 +433,7 @@ MenuRegistry.appendMenuItems([ group: '0_workbench_layout', command: { id: TogglePanelAction.ID, - title: localize('miShowPanelNoMnemonic', "Show Panel"), + title: localize('miPanelNoMnemonic', "Panel"), toggled: PanelVisibleContext }, order: 4 diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 25e9213343e..3856e6439a2 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -114,7 +114,8 @@ export class CommandCenterControl { } else { return createActionViewItem(instantiationService, action, { hoverDelegate }); } - } + }, + allowContextMenu: true }); const menu = this._disposables.add(menuService.createMenu(MenuId.CommandCenter, contextKeyService)); const menuDisposables = this._disposables.add(new DisposableStore()); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 7c4cef14f90..34edabed458 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -461,12 +461,12 @@ export class TitlebarPart extends Part implements ITitleService { this.element.style.setProperty('--zoom-factor', zoomFactor.toString()); this.rootContainer.classList.toggle('counter-zoom', this.useCounterZoom); - runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); - if (this.customMenubar) { const menubarDimension = new Dimension(0, dimension.height); this.customMenubar.layout(menubarDimension); } + + runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); } } @@ -524,12 +524,12 @@ class ToogleConfigAction extends Action2 { registerAction2(class ToogleCommandCenter extends ToogleConfigAction { constructor() { - super('window.commandCenter', localize('toggle.commandCenter', 'Show Command Center'), 1); + super('window.commandCenter', localize('toggle.commandCenter', 'Command Center'), 1); } }); registerAction2(class ToogleLayoutControl extends ToogleConfigAction { constructor() { - super('workbench.layoutControl.enabled', localize('toggle.layout', 'Show Layout Controls'), 2); + super('workbench.layoutControl.enabled', localize('toggle.layout', 'Layout Controls'), 2); } }); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 4f8aa7d5b3a..3c9b24faefd 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -496,12 +496,12 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { protected abstract activate(): void; - focus(reveal: boolean = true): void { + focus(reveal: boolean = true, revealItem?: ITreeItem): void { if (this.tree && this.root.children && this.root.children.length > 0) { // Make sure the current selected element is revealed - const selectedElement = this.tree.getSelection()[0]; - if (selectedElement && reveal) { - this.tree.reveal(selectedElement, 0.5); + const element = revealItem ?? this.tree.getSelection()[0]; + if (element && reveal) { + this.tree.reveal(element, 0.5); } // Pass Focus to Viewer @@ -784,7 +784,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { setFocus(item: ITreeItem): void { if (this.tree) { - this.focus(); + this.focus(true, item); this.tree.setFocus([item]); } } diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 637b1af74cd..258b6880e1a 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -98,6 +98,18 @@ export interface IWorkbench { ): Promise; }; + workspace: { + /** + * Forwards a port. If the current embedder implements a tunnelFactory then that will be used to make the tunnel. + * By default, openTunnel only support localhost; however, a tunnelFactory can be used to support other ips. + * + * @throws When run in an environment without a remote. + * + * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. + */ + openTunnel(tunnelOptions: ITunnelOptions): Thenable; + }; + /** * Triggers shutdown of the workbench programmatically. After this method is * called, the workbench is not usable anymore and the page needs to reload @@ -110,16 +122,6 @@ export interface IWorkbench { * has been persisted. */ shutdown: () => Promise; - - /** - * Forwards a port. If the current embedder implements a tunnelFactory then that will be used to make the tunnel. - * By default, openTunnel only support localhost; however, a tunnelFactory can be used to support other ips. - * - * @throws When run in an environment without a remote. - * - * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. - */ - openTunnel(tunnelOptions: ITunnelOptions): Thenable; } export interface IWorkbenchConstructionOptions { diff --git a/src/vs/workbench/browser/web.factory.ts b/src/vs/workbench/browser/web.factory.ts index 993aa0ce071..eef12507e51 100644 --- a/src/vs/workbench/browser/web.factory.ts +++ b/src/vs/workbench/browser/web.factory.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbench, IWorkbenchConstructionOptions, Menu } from 'vs/workbench/browser/web.api'; +import { ITunnel, ITunnelOptions, IWorkbench, IWorkbenchConstructionOptions, Menu } from 'vs/workbench/browser/web.api'; import { BrowserMain } from 'vs/workbench/browser/web.main'; import { URI } from 'vs/base/common/uri'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -152,3 +152,14 @@ export namespace window { return workbench.window.withProgress(options, task); } } + +export namespace workspace { + + /** + * {@linkcode IWorkbench.workspace IWorkbench.workspace.openTunnel} + */ + export async function openTunnel(tunnelOptions: ITunnelOptions): Promise { + const workbench = await workbenchPromise.p; + return workbench.workspace.openTunnel(tunnelOptions); + } +} diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index d0791572ccf..02ea6a2b6cb 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -137,7 +137,7 @@ export class BrowserMain extends Disposable { const openerService = accessor.get(IOpenerService); const productService = accessor.get(IProductService); const telemetryService = accessor.get(ITelemetryService); - const progessService = accessor.get(IProgressService); + const progressService = accessor.get(IProgressService); const environmentService = accessor.get(IBrowserWorkbenchEnvironmentService); const instantiationService = accessor.get(IInstantiationService); const remoteExplorerService = accessor.get(IRemoteExplorerService); @@ -169,28 +169,30 @@ export class BrowserMain extends Disposable { } }, window: { - withProgress: (options, task) => progessService.withProgress(options, task) + withProgress: (options, task) => progressService.withProgress(options, task) }, - shutdown: () => lifecycleService.shutdown(), - openTunnel: async (tunnelOptions) => { - const tunnel = await remoteExplorerService.forward({ - remote: tunnelOptions.remoteAddress, - local: tunnelOptions.localAddressPort, - name: tunnelOptions.label, - source: { - source: TunnelSource.Extension, - description: labelService.getHostLabel(Schemas.vscodeRemote, this.configuration.remoteAuthority) - }, - elevateIfNeeded: false - }); - if (!tunnel) { - throw new Error('cannot open tunnel'); - } + workspace: { + openTunnel: async (tunnelOptions) => { + const tunnel = await remoteExplorerService.forward({ + remote: tunnelOptions.remoteAddress, + local: tunnelOptions.localAddressPort, + name: tunnelOptions.label, + source: { + source: TunnelSource.Extension, + description: labelService.getHostLabel(Schemas.vscodeRemote, this.configuration.remoteAuthority) + }, + elevateIfNeeded: false + }); + if (!tunnel) { + throw new Error('cannot open tunnel'); + } - return new class extends DisposableTunnel implements ITunnel { - override localAddress!: string; - }({ port: tunnel.tunnelRemotePort, host: tunnel.tunnelRemoteHost }, tunnel.localAddress, () => tunnel.dispose()); - } + return new class extends DisposableTunnel implements ITunnel { + override localAddress!: string; + }({ port: tunnel.tunnelRemotePort, host: tunnel.tunnelRemoteHost }, tunnel.localAddress, () => tunnel.dispose()); + } + }, + shutdown: () => lifecycleService.shutdown() }; }); } @@ -293,6 +295,11 @@ export class BrowserMain extends Disposable { ]); userDataProfilesService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(PROFILES_ENABLEMENT_CONFIG)) { + userDataProfilesService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); + } + })); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index b042c202d98..c8728506774 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -466,11 +466,6 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': 'both', 'description': localize('layoutControlType', "Controls whether the layout control in the custom title bar is displayed as a single menu button or with multiple UI toggles."), }, - 'workbench.editor.dropIntoEditor.enabled': { - 'type': 'boolean', - 'default': true, - 'markdownDescription': localize('dropIntoEditor', "Controls whether you can drag and drop a file into a text editor by holding down `shift` (instead of opening the file in an editor)."), - }, 'workbench.experimental.layoutControl.enabled': { 'type': 'boolean', 'tags': ['experimental'], diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index a886ffaee13..6f6ab509248 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -484,8 +484,6 @@ export interface IResourceDiffEditorInput extends IBaseUntypedEditorInput { readonly modified: IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput; } -export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; - /** * A resource merge editor input compares multiple editors * highlighting the differences for merging. @@ -498,12 +496,12 @@ export interface IResourceMergeEditorInput extends IBaseUntypedEditorInput { /** * The one changed version of the file. */ - readonly input1: IResourceMergeEditorInputSide; + readonly input1: IResourceEditorInput | ITextResourceEditorInput; /** * The second changed version of the file. */ - readonly input2: IResourceMergeEditorInputSide; + readonly input2: IResourceEditorInput | ITextResourceEditorInput; /** * The base common ancestor of the file to merge. diff --git a/src/vs/workbench/contrib/bracketPairColorizer2Telemetry/browser/bracketPairColorizer2Telemetry.contribution.ts b/src/vs/workbench/contrib/bracketPairColorizer2Telemetry/browser/bracketPairColorizer2Telemetry.contribution.ts new file mode 100644 index 00000000000..c1051e9d4cf --- /dev/null +++ b/src/vs/workbench/contrib/bracketPairColorizer2Telemetry/browser/bracketPairColorizer2Telemetry.contribution.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { onUnexpectedError } from 'vs/base/common/errors'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +class BracketPairColorizer2TelemetryContribution { + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @ITelemetryService private readonly telemetryService: ITelemetryService + ) { + this.init().catch(onUnexpectedError); + } + + private async init(): Promise { + const bracketPairColorizerId = 'coenraads.bracket-pair-colorizer-2'; + + await this.extensionsWorkbenchService.queryLocal(); + const extension = this.extensionsWorkbenchService.installed.find(e => e.identifier.id === bracketPairColorizerId); + if ( + !extension || + ((extension.enablementState !== EnablementState.EnabledGlobally) && + (extension.enablementState !== EnablementState.EnabledWorkspace)) + ) { + return; + } + + const nativeBracketPairColorizationEnabledKey = 'editor.bracketPairColorization.enabled'; + const nativeColorizationEnabled = !!this.configurationService.getValue(nativeBracketPairColorizationEnabledKey); + + type BracketPairColorizer2InstalledClassification = { + owner: 'hediet'; + nativeColorizationEnabled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or not built-in bracket pair colorization is being used' }; + comment: 'We use this to understand how many users have the bracket pair colorizer extension installed (and how many of them have native bracket pair colorization enabled), as the extension does not do anything if native bracket pair colorization is enabled.'; + }; + type BracketPairColorizer2Event = { + nativeColorizationEnabled: boolean; + }; + this.telemetryService.publicLog2('bracketPairColorizer2Usage', { + nativeColorizationEnabled + }); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BracketPairColorizer2TelemetryContribution, LifecyclePhase.Restored); + diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts index 54fc34de741..f0ea2a89e33 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts @@ -20,7 +20,7 @@ export class ToggleMinimapAction extends Action2 { title: { value: localize('toggleMinimap', "Toggle Minimap"), original: 'Toggle Minimap', - mnemonicTitle: localize({ key: 'miShowMinimap', comment: ['&& denotes a mnemonic'] }, "Show &&Minimap") + mnemonicTitle: localize({ key: 'miMinimap', comment: ['&& denotes a mnemonic'] }, "&&Minimap") }, category: CATEGORIES.View, f1: true, diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 450c76ae8ad..674ea47d406 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -528,7 +528,11 @@ export class CommentNode extends Disposable { this.updateCommentBody(newComment.body); } - if (newComment.mode !== undefined && newComment.mode !== this.comment.mode) { + const isChangingMode: boolean = newComment.mode !== undefined && newComment.mode !== this.comment.mode; + + this.comment = newComment; + + if (isChangingMode) { if (newComment.mode === languages.CommentMode.Editing) { this.switchToEditMode(); } else { @@ -536,8 +540,6 @@ export class CommentNode extends Disposable { } } - this.comment = newComment; - if (newComment.label) { this._isPendingLabel.innerText = newComment.label; } else { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 99c4aae1ca9..c057977818c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -456,6 +456,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget override show(rangeOrPos: IRange | IPosition, heightInLines: number): void { this._isExpanded = true; super.show(rangeOrPos, heightInLines); + this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; this._refresh(this._commentThreadWidget.getDimensions()); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 41ed64d6d55..5f859a45b0a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -664,6 +664,7 @@ export class CommentController implements IEditorContribution { const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId!]; this.displayCommentThread(e.owner, thread, pendingCommentText); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); + this.tryUpdateReservedSpace(); }); this._commentThreadRangeDecorator.update(this.editor, commentInfo); })); @@ -841,15 +842,14 @@ export class CommentController implements IEditorContribution { return; } - private setComments(commentInfos: ICommentInfo[]): void { - if (!this.editor || !this.commentService.isCommentingEnabled) { - return; - } - - this._commentInfos = commentInfos; + private tryUpdateReservedSpace() { let lineDecorationsWidth: number = this.editor.getLayoutInfo().decorationsWidth; + const hasCommentsOrRanges = this._commentInfos.some(info => { + const hasRanges = Boolean(info.commentingRanges && (Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges.ranges).length); + return hasRanges || (info.threads.length > 0); + }); - if (this._commentInfos.some(info => Boolean(info.commentingRanges && (Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges.ranges).length))) { + if (hasCommentsOrRanges) { this._workspaceHasCommenting.set(true); if (!this._commentingRangeSpaceReserved) { this._commentingRangeSpaceReserved = true; @@ -879,7 +879,15 @@ export class CommentController implements IEditorContribution { }); } } + } + private setComments(commentInfos: ICommentInfo[]): void { + if (!this.editor || !this.commentService.isCommentingEnabled) { + return; + } + + this._commentInfos = commentInfos; + this.tryUpdateReservedSpace(); // create viewzones this.removeCommentWidgetsAndStoreCache(); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 116c74df092..a514a41b857 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -24,6 +24,12 @@ import { IWebviewService, IOverlayWebview } from 'vs/workbench/contrib/webview/b import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +export interface CustomEditorInputInitInfo { + readonly resource: URI; + readonly viewType: string; + readonly id: string; +} + export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { static create( @@ -45,7 +51,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { contentOptions: {}, extension: undefined, }); - const input = instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview, { untitledDocumentData: untitledDocumentData, oldResource: options?.oldResource }); + const input = instantiationService.createInstance(CustomEditorInput, { resource, viewType, id }, webview, { untitledDocumentData: untitledDocumentData, oldResource: options?.oldResource }); if (typeof group !== 'undefined') { input.updateGroup(group); } @@ -68,9 +74,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { private _modelRef?: IReference; constructor( - resource: URI, - viewType: string, - id: string, + init: CustomEditorInputInitInfo, webview: IOverlayWebview, options: { startsDirty?: boolean; backupId?: string; untitledDocumentData?: VSBuffer; readonly oldResource?: URI }, @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @@ -81,8 +85,8 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { @IUndoRedoService private readonly undoRedoService: IUndoRedoService, @IFileService private readonly fileService: IFileService ) { - super(id, viewType, '', webview, webviewWorkbenchService); - this._editorResource = resource; + super({ id: init.id, providedId: init.viewType, viewType: init.viewType, name: '' }, webview, webviewWorkbenchService); + this._editorResource = init.resource; this.oldResource = options.oldResource; this._defaultDirtyState = options.startsDirty; this._backupId = options.backupId; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 9bc10254662..4277c67ab6a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -100,7 +100,7 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { } const webview = reviveWebview(this._webviewService, data); - const customInput = this._instantiationService.createInstance(CustomEditorInput, data.editorResource, data.viewType, data.id, webview, { startsDirty: data.dirty, backupId: data.backupId }); + const customInput = this._instantiationService.createInstance(CustomEditorInput, { resource: data.editorResource, viewType: data.viewType, id: data.id }, webview, { startsDirty: data.dirty, backupId: data.backupId }); if (typeof data.group === 'number') { customInput.updateGroup(data.group); } @@ -196,7 +196,7 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements extension, }); - const editor = this._instantiationService.createInstance(CustomEditorInput, URI.revive(backupData.editorResource), backupData.viewType, id, webview, { backupId: backupData.backupId }); + const editor = this._instantiationService.createInstance(CustomEditorInput, { resource: URI.revive(backupData.editorResource), viewType: backupData.viewType, id }, webview, { backupId: backupData.backupId }); editor.updateGroup(0); return editor; } diff --git a/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts b/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts index 98dfb2e82f5..9e3b5b7efff 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts @@ -24,11 +24,14 @@ export async function showDebugSessionMenu(accessor: ServicesAccessor, selectAnd const quickPick = quickInputService.createQuickPick(); localDisposableStore.add(quickPick); quickPick.matchOnLabel = quickPick.matchOnDescription = quickPick.matchOnDetail = quickPick.sortByLabel = false; - quickPick.placeholder = nls.localize('moveFocusedView.selectView', 'Search for debug session by name'); - quickPick.items = _getPicks(quickPick.value, selectAndStartID, debugService, viewsService, commandService); + quickPick.placeholder = nls.localize('moveFocusedView.selectView', 'Search debug sessions by name'); + + const pickItems = _getPicksAndActiveItem(quickPick.value, selectAndStartID, debugService, viewsService, commandService); + quickPick.items = pickItems.picks; + quickPick.activeItems = pickItems.activeItems; localDisposableStore.add(quickPick.onDidChangeValue(async () => { - quickPick.items = _getPicks(quickPick.value, selectAndStartID, debugService, viewsService, commandService); + quickPick.items = _getPicksAndActiveItem(quickPick.value, selectAndStartID, debugService, viewsService, commandService).picks; })); localDisposableStore.add(quickPick.onDidAccept(() => { const selectedItem = quickPick.selectedItems[0]; @@ -39,11 +42,13 @@ export async function showDebugSessionMenu(accessor: ServicesAccessor, selectAnd quickPick.show(); } -function _getPicks(filter: string, selectAndStartID: string, debugService: IDebugService, viewsService: IViewsService, commandService: ICommandService): Array { +function _getPicksAndActiveItem(filter: string, selectAndStartID: string, debugService: IDebugService, viewsService: IViewsService, commandService: ICommandService): { picks: Array; activeItems: Array } { const debugConsolePicks: Array = []; const headerSessions: IDebugSession[] = []; + const currSession = debugService.getViewModel().focusedSession; const sessions = debugService.getModel().getSessions(false); + const activeItems: Array = []; sessions.forEach((session) => { if (session.compact && session.parentSession) { @@ -61,6 +66,9 @@ function _getPicks(filter: string, selectAndStartID: string, debugService: IDebu const pick = _createPick(session, filter, debugService, viewsService, commandService); if (pick) { debugConsolePicks.push(pick); + if (session.getId() === currSession?.getId()) { + activeItems.push(pick); + } } } }); @@ -76,7 +84,7 @@ function _getPicks(filter: string, selectAndStartID: string, debugService: IDebu accept: () => commandService.executeCommand(selectAndStartID) }); - return debugConsolePicks; + return { picks: debugConsolePicks, activeItems }; } diff --git a/src/vs/workbench/contrib/debug/node/telemetryApp.ts b/src/vs/workbench/contrib/debug/node/telemetryApp.ts index 601ff4f9b1e..6296aeb862e 100644 --- a/src/vs/workbench/contrib/debug/node/telemetryApp.ts +++ b/src/vs/workbench/contrib/debug/node/telemetryApp.ts @@ -7,7 +7,7 @@ import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; -const appender = new OneDataSystemAppender(undefined, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); +const appender = new OneDataSystemAppender(false, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); process.once('exit', () => appender.flush()); const channel = new TelemetryAppenderChannel([appender]); diff --git a/src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts b/src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts index 06ea6440b6c..c0ffe9dd115 100644 --- a/src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts @@ -49,7 +49,13 @@ suite('Debug - StreamDebugAdapter', () => { const pipeName = crypto.randomBytes(10).toString('hex'); const pipePath = platform.isWindows ? join('\\\\.\\pipe\\', pipeName) : join(tmpdir(), pipeName); - const server = net.createServer(serverConnection).listen(pipePath); + const server = await new Promise((resolve, reject) => { + const server = net.createServer(serverConnection); + server.once('listening', () => resolve(server)); + server.once('error', reject); + server.listen(pipePath); + }); + const debugAdapter = new NamedPipeDebugAdapter({ type: 'pipeServer', path: pipePath diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 9be6a5124f7..3d96c44b96f 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -10,7 +10,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { IEditSessionsWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; +import { IEditSessionsWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EDIT_SESSIONS_CONTAINER_ID, EditSessionSchemaVersion, IEditSessionsLogService, EDIT_SESSIONS_VIEW_ICON, EDIT_SESSIONS_TITLE, EDIT_SESSIONS_SCHEME, EDIT_SESSIONS_SHOW_VIEW, EDIT_SESSIONS_SIGNED_IN, EDIT_SESSIONS_DATA_VIEW_ID } from 'vs/workbench/contrib/editSessions/common/editSessions'; import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -32,13 +32,22 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace'; import { Schemas } from 'vs/base/common/network'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { EditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessionsLogService'; +import { IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { EditSessionsDataViews } from 'vs/workbench/contrib/editSessions/browser/editSessionsViews'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { EditSessionsContentProvider } from 'vs/workbench/contrib/editSessions/browser/editSessionsContentProvider'; +import { isNative } from 'vs/base/common/platform'; +import { WorkspaceFolderCountContext } from 'vs/workbench/common/contextkeys'; registerSingleton(IEditSessionsLogService, EditSessionsLogService); registerSingleton(IEditSessionsWorkbenchService, EditSessionsWorkbenchService); @@ -47,6 +56,7 @@ const continueEditSessionCommand: IAction2Options = { id: '_workbench.experimental.editSessions.actions.continueEditSession', title: { value: localize('continue edit session', "Continue Edit Session..."), original: 'Continue Edit Session...' }, category: EDIT_SESSION_SYNC_CATEGORY, + precondition: WorkspaceFolderCountContext.notEqualsTo('0'), f1: true }; const openLocalFolderCommand: IAction2Options = { @@ -63,6 +73,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo private registered = false; private continueEditSessionOptions: ContinueEditSessionItem[] = []; + private readonly shouldShowViewsContext: IContextKey; + constructor( @IEditSessionsWorkbenchService private readonly editSessionsWorkbenchService: IEditSessionsWorkbenchService, @IFileService private readonly fileService: IFileService, @@ -74,9 +86,11 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo @IDialogService private readonly dialogService: IDialogService, @IEditSessionsLogService private readonly logService: IEditSessionsLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IProductService private readonly productService: IProductService, @IConfigurationService private configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ITextModelService textModelResolverService: ITextModelService, @IQuickInputService private readonly quickInputService: IQuickInputService, @ICommandService private commandService: ICommandService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -101,6 +115,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo }); this.registerActions(); + this.registerViews(); continueEditSessionExtPoint.setHandler(extensions => { const continueEditSessionOptions: ContinueEditSessionItem[] = []; @@ -128,6 +143,26 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } this.continueEditSessionOptions = continueEditSessionOptions; }); + + this.shouldShowViewsContext = EDIT_SESSIONS_SHOW_VIEW.bindTo(this.contextKeyService); + + textModelResolverService.registerTextModelContentProvider(EDIT_SESSIONS_SCHEME, instantiationService.createInstance(EditSessionsContentProvider)); + } + + private registerViews() { + const container = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer( + { + id: EDIT_SESSIONS_CONTAINER_ID, + title: EDIT_SESSIONS_TITLE, + ctorDescriptor: new SyncDescriptor( + ViewPaneContainer, + [EDIT_SESSIONS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }] + ), + icon: EDIT_SESSIONS_VIEW_ICON, + hideIfEmpty: true + }, ViewContainerLocation.Sidebar + ); + this._register(this.instantiationService.createInstance(EditSessionsDataViews, container)); } private registerActions() { @@ -143,9 +178,32 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo this.registerContinueInLocalFolderAction(); + this.registerShowEditSessionViewAction(); + this.registered = true; } + private registerShowEditSessionViewAction() { + const that = this; + this._register(registerAction2(class ShowEditSessionView extends Action2 { + constructor() { + super({ + id: 'workbench.editSessions.actions.showEditSessions', + title: { value: localize('show edit session', "Show Edit Sessions"), original: 'Show Edit Sessions' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true, + precondition: EDIT_SESSIONS_SIGNED_IN + }); + } + + async run(accessor: ServicesAccessor) { + that.shouldShowViewsContext.set(true); + const viewsService = accessor.get(IViewsService); + await viewsService.openView(EDIT_SESSIONS_DATA_VIEW_ID); + } + })); + } + private registerContinueEditSessionAction() { const that = this; this._register(registerAction2(class ContinueEditSessionAction extends Action2 { @@ -195,7 +253,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo }); } - async run(accessor: ServicesAccessor): Promise { + async run(accessor: ServicesAccessor, editSessionId?: string): Promise { await that.progressService.withProgress({ location: ProgressLocation.Notification, title: localize('resuming edit session', 'Resuming edit session...') @@ -206,7 +264,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo }; that.telemetryService.publicLog2('editSessions.resume'); - await that.resumeEditSession(); + await that.resumeEditSession(editSessionId); }); } })); @@ -474,7 +532,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo private createPickItems(): ContinueEditSessionItem[] { const items = [...this.continueEditSessionOptions].filter((option) => option.when === undefined || this.contextKeyService.contextMatchesRules(option.when)); - if (getVirtualWorkspaceLocation(this.contextService.getWorkspace()) !== undefined) { + if (getVirtualWorkspaceLocation(this.contextService.getWorkspace()) !== undefined && isNative) { items.push(new ContinueEditSessionItem( localize('continueEditSessionItem.openInLocalFolder', 'Open In Local Folder'), openLocalFolderCommand.id, diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsContentProvider.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsContentProvider.ts new file mode 100644 index 00000000000..30d56a6c398 --- /dev/null +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsContentProvider.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; +import { EDIT_SESSIONS_SCHEME, IEditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/common/editSessions'; + +export class EditSessionsContentProvider implements ITextModelContentProvider { + + constructor( + @IEditSessionsWorkbenchService private editSessionsWorkbenchService: IEditSessionsWorkbenchService, + @IModelService private readonly modelService: IModelService, + ) { } + + async provideTextContent(uri: URI): Promise { + let model: ITextModel | null = null; + if (uri.scheme === EDIT_SESSIONS_SCHEME) { + const match = /(?[^/]+)\/(?[^/]+)\/(?.*)/.exec(uri.path.substring(1)); + if (match?.groups) { + const { ref, folderName, filePath } = match.groups; + const data = await this.editSessionsWorkbenchService.read(ref); + const content = data?.editSession.folders.find((f) => f.name === folderName)?.workingChanges.find((change) => change.relativeFilePath === filePath)?.contents; + if (content) { + model = this.modelService.createModel(content, null, uri); + } + } + } + return model; + } +} diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsViews.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsViews.ts new file mode 100644 index 00000000000..28dfa2257e4 --- /dev/null +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsViews.ts @@ -0,0 +1,188 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { TreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; +import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewsRegistry, TreeItemCollapsibleState, TreeViewItemHandleArg, ViewContainer } from 'vs/workbench/common/views'; +import { EDIT_SESSIONS_DATA_VIEW_ID, EDIT_SESSIONS_SCHEME, EDIT_SESSIONS_SHOW_VIEW, EDIT_SESSIONS_SIGNED_IN, EDIT_SESSIONS_TITLE, IEditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/common/editSessions'; +import { URI } from 'vs/base/common/uri'; +import { fromNow } from 'vs/base/common/date'; +import { Codicon } from 'vs/base/common/codicons'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; + +export class EditSessionsDataViews extends Disposable { + constructor( + container: ViewContainer, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + this.registerViews(container); + } + + private registerViews(container: ViewContainer): void { + const viewId = EDIT_SESSIONS_DATA_VIEW_ID; + const name = localize('edit sessions data', 'All Sessions'); + const treeView = this.instantiationService.createInstance(TreeView, viewId, name); + treeView.showCollapseAllAction = true; + treeView.showRefreshAction = true; + const disposable = treeView.onDidChangeVisibility(visible => { + if (visible && !treeView.dataProvider) { + disposable.dispose(); + treeView.dataProvider = this.instantiationService.createInstance(EditSessionDataViewDataProvider); + } + }); + Registry.as(Extensions.ViewsRegistry).registerViews([{ + id: viewId, + name, + ctorDescriptor: new SyncDescriptor(TreeViewPane), + canToggleVisibility: true, + canMoveView: false, + treeView, + collapsed: false, + when: ContextKeyExpr.and(EDIT_SESSIONS_SIGNED_IN, EDIT_SESSIONS_SHOW_VIEW), + order: 100, + hideByDefault: true, + }], container); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.editSessions.actions.resume', + title: localize('workbench.editSessions.actions.resume', "Resume Edit Session"), + icon: Codicon.repoPull, + menu: { + id: MenuId.ViewItemContext, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', viewId), ContextKeyExpr.regex('viewItem', /edit-session/i)), + group: 'inline' + } + }); + } + + async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { + const editSessionId = URI.parse(handle.$treeItemHandle).path.substring(1); + const commandService = accessor.get(ICommandService); + await commandService.executeCommand('workbench.experimental.editSessions.actions.resumeLatest', editSessionId); + await treeView.refresh(); + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.editSessions.actions.delete', + title: localize('workbench.editSessions.actions.delete', "Delete Edit Session"), + icon: Codicon.trash, + menu: { + id: MenuId.ViewItemContext, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', viewId), ContextKeyExpr.regex('viewItem', /edit-session/i)), + group: 'inline' + } + }); + } + + async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { + const editSessionId = URI.parse(handle.$treeItemHandle).path.substring(1); + const dialogService = accessor.get(IDialogService); + const editSessionWorkbenchService = accessor.get(IEditSessionsWorkbenchService); + const result = await dialogService.confirm({ + message: localize('confirm delete', 'Are you sure you want to permanently delete the edit session with ref {0}? You cannot undo this action.', editSessionId), + type: 'warning', + title: EDIT_SESSIONS_TITLE + }); + if (result.confirmed) { + await editSessionWorkbenchService.delete(editSessionId); + await treeView.refresh(); + } + } + }); + } +} + +class EditSessionDataViewDataProvider implements ITreeViewDataProvider { + constructor( + @IEditSessionsWorkbenchService private readonly editSessionsWorkbenchService: IEditSessionsWorkbenchService + ) { } + + async getChildren(element?: ITreeItem): Promise { + if (!element) { + return this.getAllEditSessions(); + } + + const [ref, folderName, filePath] = URI.parse(element.handle).path.substring(1).split('/'); + + if (ref && !folderName) { + return this.getEditSession(ref); + } else if (ref && folderName && !filePath) { + return this.getEditSessionFolderContents(ref, folderName); + } + + return []; + } + + private async getAllEditSessions(): Promise { + const allEditSessions = await this.editSessionsWorkbenchService.list(); + return allEditSessions.map((session) => { + const resource = URI.from({ scheme: EDIT_SESSIONS_SCHEME, authority: 'remote-session-content', path: `/${session.ref}` }); + return { + handle: resource.toString(), + collapsibleState: TreeItemCollapsibleState.Collapsed, + label: { label: session.ref }, + description: fromNow(session.created, true), + themeIcon: Codicon.repo, + contextValue: `edit-session` + }; + }); + } + + private async getEditSession(ref: string): Promise { + const data = await this.editSessionsWorkbenchService.read(ref); + + if (!data) { + return []; + } + + return data.editSession.folders.map((folder) => { + const resource = URI.from({ scheme: EDIT_SESSIONS_SCHEME, authority: 'remote-session-content', path: `/${data.ref}/${folder.name}` }); + return { + handle: resource.toString(), + collapsibleState: TreeItemCollapsibleState.Collapsed, + label: { label: folder.name }, + themeIcon: Codicon.folder + }; + }); + } + + private async getEditSessionFolderContents(ref: string, folderName: string): Promise { + const data = await this.editSessionsWorkbenchService.read(ref); + + if (!data) { + return []; + } + + return (data.editSession.folders.find((folder) => folder.name === folderName)?.workingChanges ?? []).map((change) => { + const resource = URI.from({ scheme: EDIT_SESSIONS_SCHEME, authority: 'remote-session-content', path: `/${data.ref}/${folderName}/${change.relativeFilePath}` }); + return { + handle: resource.toString(), + resourceUri: resource, + collapsibleState: TreeItemCollapsibleState.None, + label: { label: change.relativeFilePath }, + themeIcon: Codicon.file, + command: { + id: API_OPEN_EDITOR_COMMAND_ID, + title: localize('open file', 'Open File'), + arguments: [resource, undefined, undefined] + } + }; + }); + } +} diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts index b63b550ab35..36f51b145a0 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts @@ -14,7 +14,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IRequestService } from 'vs/platform/request/common/request'; import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { createSyncHeaders, IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; +import { createSyncHeaders, IAuthenticationProvider, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -122,6 +122,21 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } } + async list(): Promise { + await this.initialize(); + if (!this.initialized) { + throw new Error(`Unable to list edit sessions.`); + } + + try { + return this.storeClient?.getAllRefs('editSessions') ?? []; + } catch (ex) { + this.logService.error(ex); + } + + return []; + } + private async initialize() { if (this.initialized) { return; @@ -340,7 +355,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes constructor() { super({ id: 'workbench.editSessions.actions.resetAuth', - title: localize('reset auth', 'Sign Out'), + title: localize('reset auth.v2', 'Sign Out of Edit Sessions'), category: EDIT_SESSION_SYNC_CATEGORY, precondition: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), menu: [{ diff --git a/src/vs/workbench/contrib/editSessions/common/editSessions.ts b/src/vs/workbench/contrib/editSessions/common/editSessions.ts index 789976a50e5..c4a2801d9ff 100644 --- a/src/vs/workbench/contrib/editSessions/common/editSessions.ts +++ b/src/vs/workbench/contrib/editSessions/common/editSessions.ts @@ -3,11 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Codicon } from 'vs/base/common/codicons'; import { localize } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = { original: 'Edit Sessions', @@ -21,6 +24,7 @@ export interface IEditSessionsWorkbenchService { read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>; write(editSession: EditSession): Promise; delete(ref: string): Promise; + list(): Promise; } export const IEditSessionsLogService = createDecorator('IEditSessionsLogService'); @@ -65,3 +69,13 @@ export interface EditSession { export const EDIT_SESSIONS_SIGNED_IN_KEY = 'editSessionsSignedIn'; export const EDIT_SESSIONS_SIGNED_IN = new RawContextKey(EDIT_SESSIONS_SIGNED_IN_KEY, false); + +export const EDIT_SESSIONS_CONTAINER_ID = 'workbench.view.editSessions'; +export const EDIT_SESSIONS_DATA_VIEW_ID = 'workbench.views.editSessions.data'; +export const EDIT_SESSIONS_TITLE = localize('edit sessions', 'Edit Sessions'); + +export const EDIT_SESSIONS_VIEW_ICON = registerIcon('edit-sessions-view-icon', Codicon.cloudDownload, localize('editSessionViewIcon', 'View icon of the edit sessions view.')); + +export const EDIT_SESSIONS_SHOW_VIEW = new RawContextKey('editSessionsShowView', false); + +export const EDIT_SESSIONS_SCHEME = 'vscode-edit-sessions'; diff --git a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index 07917f285e5..138436bc52f 100644 --- a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -28,6 +28,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { Event } from 'vs/base/common/event'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; const folderName = 'test-folder'; const folderUri = URI.file(`/${folderName}`); @@ -76,6 +82,17 @@ suite('Edit session sync', () => { // Stub repositories instantiationService.stub(ISCMService, '_repositories', new Map()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(IThemeService, new class extends mock() { + override onDidColorThemeChange = Event.None; + override onDidFileIconThemeChange = Event.None; + }); + instantiationService.stub(IViewDescriptorService, { + onDidChangeLocation: Event.None + }); + instantiationService.stub(ITextModelService, new class extends mock() { + override registerTextModelContentProvider = () => ({ dispose: () => { } }); + }); editSessionsContribution = instantiationService.createInstance(EditSessionsContribution); }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 84eaa15be18..c5a4a64a417 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -13,7 +13,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, extensionVerifiedPublisherIconColor } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; @@ -119,11 +119,12 @@ export class Renderer implements IPagedRenderer { const actions = [ this.instantiationService.createInstance(ExtensionStatusLabelAction), this.instantiationService.createInstance(MigrateDeprecatedExtensionAction, true), - this.instantiationService.createInstance(UpdateAction), reloadAction, + this.instantiationService.createInstance(UpdateAction), this.instantiationService.createInstance(InstallDropdownAction), this.instantiationService.createInstance(InstallingLabelAction), this.instantiationService.createInstance(SetLanguageAction), + this.instantiationService.createInstance(ClearLanguageAction), this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), this.instantiationService.createInstance(WebInstallAction), diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index 6040e37a790..030f0b5ca87 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -216,6 +216,7 @@ suite('ExtensionRecommendationsService Test', () => { onDidInstallExtensions: didInstallEvent.event, onUninstallExtension: uninstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, + onDidChangeProfileExtensions: Event.None, async getInstalled() { return []; }, async canInstall() { return true; }, async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, @@ -447,7 +448,7 @@ suite('ExtensionRecommendationsService Test', () => { }); }); - test.skip('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => { + test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => { const storageService = instantiationService.get(IStorageService); const workspaceIgnoredRecommendations = ['ms-dotnettools.csharp']; // ignore a stored recommendation and a workspace recommendation. @@ -462,9 +463,7 @@ suite('ExtensionRecommendationsService Test', () => { await testObject.activationPromise; const recommendations = testObject.getAllRecommendationsWithReason(); - assert.ok(recommendations['ms-python.python'], 'ms-python.python extension shall exist'); - assert.ok(!recommendations['mockpublisher2.mockextension2'], 'mockpublisher2.mockextension2 extension shall not exist'); - assert.ok(!recommendations['ms-dotnettools.csharp'], 'ms-dotnettools.csharp extension shall not exist'); + assert.deepStrictEqual(Object.keys(recommendations), ['ms-python.python', 'mockpublisher1.mockextension1']); }); test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', async () => { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index e90d052ae29..6e15cf4d9c8 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -989,7 +989,7 @@ registerAction2(class extends Action2 { menu: { id: MenuId.ViewTitle, group: 'navigation', - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ViewHasSomeCollapsibleRootItemContext), + when: ContextKeyExpr.equals('view', VIEW_ID), order: 40 } }); @@ -1001,26 +1001,3 @@ registerAction2(class extends Action2 { explorerView.collapseAll(); } }); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'workbench.files.action.expandExplorerFolders', - title: { value: nls.localize('expandExplorerFolders', "Expand Folders in Explorer"), original: 'Expand Folders in Explorer' }, - f1: true, - icon: Codicon.expandAll, - menu: { - id: MenuId.ViewTitle, - group: 'navigation', - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ViewHasSomeCollapsibleRootItemContext.toNegated()), - order: 40 - } - }); - } - - run(accessor: ServicesAccessor) { - const viewsService = accessor.get(IViewsService); - const explorerView = viewsService.getViewWithId(VIEW_ID) as ExplorerView; - explorerView.expandAll(); - } -}); diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index c6442b9ce9c..3db0fd13064 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -283,7 +283,7 @@ export class InteractiveEditorSerializer implements IEditorSerializer { } canSerialize(): boolean { - return this.configurationService.getValue(InteractiveWindowSetting.interactiveWindowHotExit); + return this.configurationService.getValue(InteractiveWindowSetting.interactiveWindowRestore); } serialize(input: EditorInput): string { @@ -763,10 +763,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis default: true, markdownDescription: localize('interactiveWindow.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.") }, - [InteractiveWindowSetting.interactiveWindowHotExit]: { + [InteractiveWindowSetting.interactiveWindowRestore]: { type: 'boolean', default: false, - markdownDescription: localize('interactiveWindow.hotExit', "Controls whether the interactive window sessions should be restored when the workspace reloads.") + markdownDescription: localize('interactiveWindow.restore', "Controls whether the Interactive Window sessions/history should be restored across window reloads. Whether the state of controllers used in Interactive Windows is persisted across window reloads are controlled by extensions contributing controllers.") } } }); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts index edda5c7f25f..c98f2e6a7e0 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts @@ -9,5 +9,5 @@ export const INTERACTIVE_INPUT_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top export const InteractiveWindowSetting = { interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell', - interactiveWindowHotExit: 'interactiveWindow.hotExit' + interactiveWindowRestore: 'interactiveWindow.restore' }; diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index 4d39773daed..3aaaa1c9df6 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -411,7 +411,7 @@ export class MarkersTable extends Disposable implements IProblemsWidget { const items: MarkerTableItem[] = []; for (const resourceMarker of this.resourceMarkers) { for (const marker of resourceMarker.markers) { - if (marker.resource.scheme === network.Schemas.walkThrough || marker.resource.scheme === network.Schemas.walkThroughSnippet) { + if (marker.resource.scheme === network.Schemas.walkThrough || marker.resource.scheme === network.Schemas.walkThroughSnippet || marker.resource.scheme === network.Schemas.vscodeSourceControl) { continue; } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 778ae2c5497..103d6512066 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -19,7 +19,7 @@ import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { ILabelService } from 'vs/platform/label/common/label'; -import { basename, isEqual } from 'vs/base/common/resources'; +import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; @@ -185,7 +185,7 @@ export class ResourceMarkersRenderer implements ITreeRenderer { } private filterResourceMarkers(resourceMarkers: ResourceMarkers): TreeFilterResult { - if (resourceMarkers.resource.scheme === network.Schemas.walkThrough || resourceMarkers.resource.scheme === network.Schemas.walkThroughSnippet) { + if (resourceMarkers.resource.scheme === network.Schemas.walkThrough || resourceMarkers.resource.scheme === network.Schemas.walkThroughSnippet || resourceMarkers.resource.scheme === network.Schemas.vscodeSourceControl) { return false; } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 3c028ec8ebd..2f1dfdaef94 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -7,17 +7,37 @@ import { Codicon } from 'vs/base/common/codicons'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; -import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; import { ctxIsMergeEditor, ctxMergeEditorLayout } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +abstract class MergeEditorAction extends Action2 { + constructor(desc: Readonly) { + super(desc); + } + + run(accessor: ServicesAccessor): void { + const { activeEditorPane } = accessor.get(IEditorService); + if (activeEditorPane instanceof MergeEditor) { + const vm = activeEditorPane.viewModel.get(); + if (!vm) { + return; + } + this.runWithViewModel(vm, accessor); + } + } + + abstract runWithViewModel(viewModel: MergeEditorViewModel, accessor: ServicesAccessor): void; +} + export class OpenMergeEditor extends Action2 { constructor() { super({ @@ -28,14 +48,15 @@ export class OpenMergeEditor extends Action2 { run(accessor: ServicesAccessor, ...args: unknown[]): void { const validatedArgs = IRelaxedOpenArgs.validate(args[0]); - const input: IResourceMergeEditorInput = { - base: { resource: validatedArgs.base }, - input1: { resource: validatedArgs.input1.uri, label: validatedArgs.input1.title, description: validatedArgs.input1.description, detail: validatedArgs.input1.detail }, - input2: { resource: validatedArgs.input2.uri, label: validatedArgs.input2.title, description: validatedArgs.input2.description, detail: validatedArgs.input2.detail }, - result: { resource: validatedArgs.output }, - options: { preserveFocus: true } - }; - accessor.get(IEditorService).openEditor(input); + const instaService = accessor.get(IInstantiationService); + const input = instaService.createInstance( + MergeEditorInput, + validatedArgs.base, + validatedArgs.input1, + validatedArgs.input2, + validatedArgs.output, + ); + accessor.get(IEditorService).openEditor(input, { preserveFocus: true, override: EditorResolution.DISABLED }); } } @@ -167,7 +188,7 @@ const mergeEditorCategory: ILocalizedString = { original: 'Merge Editor', }; -export class OpenResultResource extends Action2 { +export class OpenResultResource extends MergeEditorAction { constructor() { super({ id: 'merge.openResult', @@ -187,15 +208,13 @@ export class OpenResultResource extends Action2 { }); } - async run(accessor: ServicesAccessor): Promise { + override runWithViewModel(viewModel: MergeEditorViewModel, accessor: ServicesAccessor): void { const editorService = accessor.get(IEditorService); - if (editorService.activeEditor instanceof MergeEditorInput) { - editorService.openEditor({ resource: editorService.activeEditor.result }); - } + editorService.openEditor({ resource: viewModel.model.result.uri }); } } -export class GoToNextConflict extends Action2 { +export class GoToNextConflict extends MergeEditorAction { constructor() { super({ id: 'merge.goToNextConflict', @@ -218,15 +237,12 @@ export class GoToNextConflict extends Action2 { }); } - run(accessor: ServicesAccessor): void { - const { activeEditorPane } = accessor.get(IEditorService); - if (activeEditorPane instanceof MergeEditor) { - activeEditorPane.viewModel.get()?.goToNextModifiedBaseRange(true); - } + override runWithViewModel(viewModel: MergeEditorViewModel): void { + viewModel.goToNextModifiedBaseRange(true); } } -export class GoToPreviousConflict extends Action2 { +export class GoToPreviousConflict extends MergeEditorAction { constructor() { super({ id: 'merge.goToPreviousConflict', @@ -252,15 +268,12 @@ export class GoToPreviousConflict extends Action2 { }); } - run(accessor: ServicesAccessor): void { - const { activeEditorPane } = accessor.get(IEditorService); - if (activeEditorPane instanceof MergeEditor) { - activeEditorPane.viewModel.get()?.goToPreviousModifiedBaseRange(true); - } + override runWithViewModel(viewModel: MergeEditorViewModel): void { + viewModel.goToPreviousModifiedBaseRange(true); } } -export class ToggleActiveConflictInput1 extends Action2 { +export class ToggleActiveConflictInput1 extends MergeEditorAction { constructor() { super({ id: 'merge.toggleActiveConflictInput1', @@ -277,19 +290,12 @@ export class ToggleActiveConflictInput1 extends Action2 { }); } - run(accessor: ServicesAccessor): void { - const { activeEditorPane } = accessor.get(IEditorService); - if (activeEditorPane instanceof MergeEditor) { - const vm = activeEditorPane.viewModel.get(); - if (!vm) { - return; - } - vm.toggleActiveConflict(1); - } + override runWithViewModel(viewModel: MergeEditorViewModel): void { + viewModel.toggleActiveConflict(1); } } -export class ToggleActiveConflictInput2 extends Action2 { +export class ToggleActiveConflictInput2 extends MergeEditorAction { constructor() { super({ id: 'merge.toggleActiveConflictInput2', @@ -306,19 +312,12 @@ export class ToggleActiveConflictInput2 extends Action2 { }); } - run(accessor: ServicesAccessor): void { - const { activeEditorPane } = accessor.get(IEditorService); - if (activeEditorPane instanceof MergeEditor) { - const vm = activeEditorPane.viewModel.get(); - if (!vm) { - return; - } - vm.toggleActiveConflict(2); - } + override runWithViewModel(viewModel: MergeEditorViewModel): void { + viewModel.toggleActiveConflict(2); } } -export class CompareInput1WithBaseCommand extends Action2 { +export class CompareInput1WithBaseCommand extends MergeEditorAction { constructor() { super({ id: 'mergeEditor.compareInput1WithBase', @@ -336,14 +335,14 @@ export class CompareInput1WithBaseCommand extends Action2 { menu: { id: MenuId.MergeInput1Toolbar } }); } - run(accessor: ServicesAccessor, ...args: unknown[]): void { - const editorService = accessor.get(IEditorService); + + override runWithViewModel(viewModel: MergeEditorViewModel, accessor: ServicesAccessor): void { const commandService = accessor.get(ICommandService); - mergeEditorCompare(editorService, commandService, 1); + mergeEditorCompare(viewModel, commandService, 1); } } -export class CompareInput2WithBaseCommand extends Action2 { +export class CompareInput2WithBaseCommand extends MergeEditorAction { constructor() { super({ id: 'mergeEditor.compareInput2WithBase', @@ -361,32 +360,25 @@ export class CompareInput2WithBaseCommand extends Action2 { menu: { id: MenuId.MergeInput2Toolbar } }); } - run(accessor: ServicesAccessor, ...args: unknown[]): void { - const editorService = accessor.get(IEditorService); + + override runWithViewModel(viewModel: MergeEditorViewModel, accessor: ServicesAccessor): void { const commandService = accessor.get(ICommandService); - mergeEditorCompare(editorService, commandService, 2); + mergeEditorCompare(viewModel, commandService, 2); } } -function mergeEditorCompare(editorService: IEditorService, commandService: ICommandService, inputNumber: 1 | 2) { - const { activeEditorPane } = editorService; - if (activeEditorPane instanceof MergeEditor) { - if (!activeEditorPane.model) { - return; - } - - const base = activeEditorPane.model.base.uri; - const input = inputNumber === 1 ? activeEditorPane.model.input1.uri : activeEditorPane.model.input2.uri; - - openDiffEditor(commandService, base, input); - } +function mergeEditorCompare(viewModel: MergeEditorViewModel, commandService: ICommandService, inputNumber: 1 | 2) { + const model = viewModel.model; + const base = model.base.uri; + const input = inputNumber === 1 ? model.input1.uri : model.input2.uri; + openDiffEditor(commandService, base, input); } function openDiffEditor(commandService: ICommandService, left: URI, right: URI, label?: string) { commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, left, right, label); } -export class OpenBaseFile extends Action2 { +export class OpenBaseFile extends MergeEditorAction { constructor() { super({ id: 'merge.openBaseEditor', @@ -400,15 +392,58 @@ export class OpenBaseFile extends Action2 { }); } - run(accessor: ServicesAccessor): void { + override runWithViewModel(viewModel: MergeEditorViewModel, accessor: ServicesAccessor): void { const openerService = accessor.get(IOpenerService); - const { activeEditorPane } = accessor.get(IEditorService); - if (activeEditorPane instanceof MergeEditor) { - const vm = activeEditorPane.viewModel.get(); - if (!vm) { - return; - } - openerService.open(vm.model.base.uri); - } + openerService.open(viewModel.model.base.uri); + } +} + +export class AcceptAllInput1 extends MergeEditorAction { + constructor() { + super({ + id: 'merge.acceptAllInput1', + category: mergeEditorCategory, + title: { + value: localize( + 'merge.acceptAllInput1', + 'Accept All Changes from Left' + ), + original: 'Accept All Changes from Left', + }, + f1: true, + precondition: ctxIsMergeEditor, + menu: [ + { id: MenuId.MergeInput1Toolbar, } + ] + }); + } + + override runWithViewModel(viewModel: MergeEditorViewModel): void { + viewModel.acceptAll(1); + } +} + +export class AcceptAllInput2 extends MergeEditorAction { + constructor() { + super({ + id: 'merge.acceptAllInput2', + category: mergeEditorCategory, + title: { + value: localize( + 'merge.acceptAllInput2', + 'Accept All Changes from Right' + ), + original: 'Accept All Changes from Right', + }, + f1: true, + precondition: ctxIsMergeEditor, + menu: [ + { id: MenuId.MergeInput2Toolbar, } + ] + }); + } + + override runWithViewModel(viewModel: MergeEditorViewModel): void { + viewModel.acceptAll(2); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 0f2b38ea5b4..17df90e7d26 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -10,15 +10,16 @@ import { localize } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { IResourceMergeEditorInput } from 'vs/workbench/common/editor'; +import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; interface MergeEditorContents { languageId: string; @@ -98,6 +99,8 @@ export class MergeEditorOpenContents extends Action2 { async run(accessor: ServicesAccessor): Promise { const service = accessor.get(IWorkbenchFileService); + const instaService = accessor.get(IInstantiationService); + const editorService = accessor.get(IEditorService); const inputService = accessor.get(IQuickInputService); const clipboardService = accessor.get(IClipboardService); const textModelService = accessor.get(ITextModelService); @@ -151,12 +154,13 @@ export class MergeEditorOpenContents extends Action2 { setLanguageId(resultUri, content.languageId), ]); - const input: IResourceMergeEditorInput = { - base: { resource: baseUri }, - input1: { resource: input1Uri, label: 'Input 1', description: 'Input 1', detail: '(from JSON)' }, - input2: { resource: input2Uri, label: 'Input 2', description: 'Input 2', detail: '(from JSON)' }, - result: { resource: resultUri }, - }; - accessor.get(IEditorService).openEditor(input); + const input = instaService.createInstance( + MergeEditorInput, + baseUri, + { uri: input1Uri, title: 'Input 1', description: 'Input 1', detail: '(from JSON)' }, + { uri: input2Uri, title: 'Input 2', description: 'Input 2', detail: '(from JSON)' }, + resultUri, + ); + editorService.openEditor(input, { override: EditorResolution.DISABLED }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index c06a6e0447c..8120b688a8e 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; -import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, OpenResultResource, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; +import { AcceptAllInput1, AcceptAllInput2, CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, OpenResultResource, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditor, MergeEditorResolverContribution, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; @@ -51,6 +51,9 @@ registerAction2(ToggleActiveConflictInput2); registerAction2(CompareInput1WithBaseCommand); registerAction2(CompareInput2WithBaseCommand); +registerAction2(AcceptAllInput1); +registerAction2(AcceptAllInput2); + Registry .as(WorkbenchExtensions.Workbench) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 50470d8df66..9a4286f26b6 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -140,8 +140,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements override toUntyped(): IResourceMergeEditorInput { return { - input1: { resource: this.input1.uri, label: this.input1.title, description: this.input1.description, detail: this.input1.detail }, - input2: { resource: this.input2.uri, label: this.input2.title, description: this.input2.description, detail: this.input2.detail }, + input1: { resource: this.input1.uri, label: this.input1.title, description: this.input1.description }, + input2: { resource: this.input2.uri, label: this.input2.title, description: this.input2.description }, base: { resource: this.base }, result: { resource: this.result }, options: { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts index 06401136b7a..2f769dadd9b 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts @@ -24,6 +24,10 @@ export class EditorWorkerServiceDiffComputer implements IDiffComputer { async computeDiff(textModel1: ITextModel, textModel2: ITextModel): Promise { const diffs = await this.editorWorkerService.computeDiff(textModel1.uri, textModel2.uri, false, 1000); + if (textModel1.isDisposed() || textModel2.isDisposed()) { + // In the meantime, models could be disposed -> early return + return { diffs: null }; + } if (!diffs) { return { diffs: null }; } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 14e739314c3..29e74d70adf 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -65,37 +65,39 @@ export class InputCodeEditorView extends CodeEditorView { position: MinimapPosition.Gutter, color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor }, }, - overviewRuler: { + overviewRuler: modifiedBaseRange.isConflicting ? { position: OverviewRulerLane.Center, color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor }, - } + } : undefined } }); - const inputDiffs = modifiedBaseRange.getInputDiffs(this.inputNumber); - for (const diff of inputDiffs) { - const range = diff.outputRange.toInclusiveRange(); - if (range) { - result.push({ - range, - options: { - className: `merge-editor-diff ${inputClassName}`, - description: 'Merge Editor', - isWholeLine: true, - } - }); - } - - if (diff.rangeMappings) { - for (const d of diff.rangeMappings) { + if (modifiedBaseRange.isConflicting) { + const inputDiffs = modifiedBaseRange.getInputDiffs(this.inputNumber); + for (const diff of inputDiffs) { + const range = diff.outputRange.toInclusiveRange(); + if (range) { result.push({ - range: d.outputRange, + range, options: { - className: `merge-editor-diff-word ${inputClassName}`, - description: 'Merge Editor' + className: `merge-editor-diff ${inputClassName}`, + description: 'Merge Editor', + isWholeLine: true, } }); } + + if (diff.rangeMappings) { + for (const d of diff.rangeMappings) { + result.push({ + range: d.outputRange, + options: { + className: `merge-editor-diff-word ${inputClassName}`, + description: 'Merge Editor' + } + }); + } + } } } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index e1557eb0c9a..04ee5e94264 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CompareResult } from 'vs/base/common/arrays'; +import { BugIndicatingError } from 'vs/base/common/errors'; import { autorun, derived } from 'vs/base/common/observable'; import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; @@ -11,6 +12,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; +import { EditorGutter } from 'vs/workbench/contrib/mergeEditor/browser/view/editorGutter'; import { CodeEditorView } from './codeEditorView'; export class ResultCodeEditorView extends CodeEditorView { @@ -61,38 +63,41 @@ export class ResultCodeEditorView extends CodeEditorView { position: MinimapPosition.Gutter, color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor }, }, - overviewRuler: { + overviewRuler: modifiedBaseRange.isConflicting ? { position: OverviewRulerLane.Center, color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor }, - } + } : undefined } }); } } - for (const diff of m.rights) { - const range = diff.outputRange.toInclusiveRange(); - if (range) { - result.push({ - range, - options: { - className: `merge-editor-diff result`, - description: 'Merge Editor', - isWholeLine: true, - } - }); - } - if (diff.rangeMappings) { - for (const d of diff.rangeMappings) { + if (!modifiedBaseRange || modifiedBaseRange.isConflicting) { + for (const diff of m.rights) { + const range = diff.outputRange.toInclusiveRange(); + if (range) { result.push({ - range: d.outputRange, + range, options: { - className: `merge-editor-diff-word result`, - description: 'Merge Editor' + className: `merge-editor-diff result`, + description: 'Merge Editor', + isWholeLine: true, } }); } + + if (diff.rangeMappings) { + for (const d of diff.rangeMappings) { + result.push({ + range: d.outputRange, + options: { + className: `merge-editor-diff-word result`, + description: 'Merge Editor' + } + }); + } + } } } } @@ -106,6 +111,14 @@ export class ResultCodeEditorView extends CodeEditorView { this._register(applyObservableDecorations(this.editor, this.decorations)); + this.htmlElements.gutterDiv.style.width = '5px'; + + this._register( + new EditorGutter(this.editor, this.htmlElements.gutterDiv, { + getIntersectingGutterItems: (range, reader) => [], + createView: (item, target) => { throw new BugIndicatingError(); }, + }) + ); this._register(autorun('update remainingConflicts label', reader => { const model = this.model.read(reader); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 46aee59e160..b17a1e8eebf 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -47,7 +47,7 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v import { ctxMergeBaseUri, ctxIsMergeEditor, ctxMergeEditorLayout, ctxMergeResultUri, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorResolverService, MergeEditorInputFactoryFunction, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { EditorInputFactoryFunction, IEditorResolverService, MergeEditorInputFactoryFunction, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import './colors'; import { InputCodeEditorView } from './editors/inputCodeEditorView'; @@ -559,6 +559,28 @@ export class MergeEditorResolverContribution extends Disposable { ) { super(); + const editorInputFactory: EditorInputFactoryFunction = (editor) => { + return { + editor: instantiationService.createInstance( + MergeEditorInput, + editor.resource, + { + uri: editor.resource, + title: '', + description: '', + detail: '' + }, + { + uri: editor.resource, + title: '', + description: '', + detail: '' + }, + editor.resource + ) + }; + }; + const mergeEditorInputFactory: MergeEditorInputFactoryFunction = (mergeEditor: IResourceMergeEditorInput): EditorInputWithOptions => { return { editor: instantiationService.createInstance( @@ -566,15 +588,15 @@ export class MergeEditorResolverContribution extends Disposable { mergeEditor.base.resource, { uri: mergeEditor.input1.resource, - title: mergeEditor.input1.label ?? basename(mergeEditor.input1.resource), - description: mergeEditor.input1.description ?? '', - detail: mergeEditor.input1.detail + title: basename(mergeEditor.input1.resource), + description: '', + detail: '' }, { uri: mergeEditor.input2.resource, - title: mergeEditor.input2.label ?? basename(mergeEditor.input2.resource), - description: mergeEditor.input2.description ?? '', - detail: mergeEditor.input2.detail + title: basename(mergeEditor.input2.resource), + description: '', + detail: '' }, mergeEditor.result.resource ) @@ -584,13 +606,14 @@ export class MergeEditorResolverContribution extends Disposable { this._register(editorResolverService.registerEditor( `*`, { - id: DEFAULT_EDITOR_ASSOCIATION.id, - label: DEFAULT_EDITOR_ASSOCIATION.displayName, + id: MergeEditorInput.ID, + label: localize('editor.mergeEditor.label', "Merge Editor"), detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, - priority: RegisteredEditorPriority.builtin + priority: RegisteredEditorPriority.option }, {}, { + createEditorInput: editorInputFactory, createMergeEditorInput: mergeEditorInputFactory } )); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index de910cceaee..37643b3fb6b 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -143,4 +143,17 @@ export class MergeEditorViewModel { ); }); } + + public acceptAll(inputNumber: 1 | 2): void { + transaction(tx => { + /** @description Toggle Active Conflict */ + for (const range of this.model.modifiedBaseRanges.get()) { + this.setState( + range, + this.model.getState(range).get().withInputValue(inputNumber, true), + tx + ); + } + }); + } } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 7b190d89c14..70d6930241c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -544,8 +544,8 @@ registerAction2(class RevealRunningCellAction extends NotebookAction { constructor() { super({ id: REVEAL_RUNNING_CELL, - title: localize('revealRunningCell', "Go To Running Cell"), - tooltip: localize('revealRunningCell', "Go To Running Cell"), + title: localize('revealRunningCell', "Go to Running Cell"), + tooltip: localize('revealRunningCell', "Go to Running Cell"), shortTitle: localize('revealRunningCellShort', "Go To"), precondition: NOTEBOOK_HAS_RUNNING_CELL, menu: [ diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts index 870d4b2d719..ac666a052eb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts @@ -329,7 +329,6 @@ export class StatefulMarkdownCell extends Disposable { width: width, height: editorHeight }, - enableDropIntoEditor: true, // overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() }, { contributions: this.notebookEditor.creationOptions.cellEditorContributions diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 949c8dd25b5..8e3ab0b4640 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -275,7 +275,6 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende width: 0, height: 0 }, - enableDropIntoEditor: true, }, { contributions: this.notebookEditor.creationOptions.cellEditorContributions }); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 90a8b212b6c..2ad3516e3e9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -2367,6 +2367,7 @@ export class SettingsTree extends WorkbenchObjectTree { filter: instantiationService.createInstance(SettingsTreeFilter, viewState), smoothScrolling: configurationService.getValue('workbench.list.smoothScrolling'), multipleSelectionSupport: false, + findWidgetEnabled: false }, instantiationService, contextKeyService, diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 1680881aeb1..2f96395016a 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -102,12 +102,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo changed = true; } - // Profiles - if (typeof config.workbench?.experimental?.settingsProfiles?.enabled === 'boolean' && config.workbench.experimental.settingsProfiles.enabled !== this.settingsProfilesEnabled) { - this.settingsProfilesEnabled = config.workbench.experimental.settingsProfiles.enabled; - changed = true; - } - // On linux turning on accessibility support will also pass this flag to the chrome renderer, thus a restart is required if (isLinux && typeof config.editor?.accessibilitySupport === 'string' && config.editor.accessibilitySupport !== this.accessibilitySupport) { this.accessibilitySupport = config.editor.accessibilitySupport; @@ -123,6 +117,12 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } } + // Profiles + if (typeof config.workbench?.experimental?.settingsProfiles?.enabled === 'boolean' && config.workbench.experimental.settingsProfiles.enabled !== this.settingsProfilesEnabled) { + this.settingsProfilesEnabled = config.workbench.experimental.settingsProfiles.enabled; + changed = true; + } + // Notify only when changed and we are the focused window (avoids notification spam across windows) if (notify && changed) { this.doConfirm( diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 91045b0fb95..bd8178fcec7 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -281,7 +281,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr } // Show for remote windows on the desktop, but not when in code server web - if (this.remoteAuthority && !isWeb) { + if (this.remoteAuthority && (!isWeb || this.environmentService.options?.webSocketFactory)) { const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.remoteAuthority) || this.remoteAuthority; switch (this.connectionState) { case 'initializing': diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index f3d20816324..001c769f36a 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -1067,7 +1067,6 @@ export class TunnelPanelDescriptor implements IViewDescriptor { readonly ctorDescriptor: SyncDescriptor; readonly canToggleVisibility = true; readonly hideByDefault = false; - readonly workspace = true; // group is not actually used for views that are not extension contributed. Use order instead. readonly group = 'details@0'; // -500 comes from the remote explorer viewOrderDelegate diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 57e7afbf13a..75929726a57 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -19,10 +19,6 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { stripIcons } from 'vs/base/common/iconLabels'; import { Schemas } from 'vs/base/common/network'; import { Iterable } from 'vs/base/common/iterator'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; function getCount(repository: ISCMRepository): number { if (typeof repository.provider.count === 'number') { @@ -276,46 +272,3 @@ export class SCMActiveResourceContextKeyController implements IWorkbenchContribu this.repositoryDisposables.clear(); } } - -class SCMInputTextDocumentLabelFormatter { - - readonly separator = '/'; - - readonly label = (resource: URI) => { - const match = /git\/(?scm[\d+])\/input/i.exec(resource.path); - - if (match?.groups === undefined) { - return resource.toString(); - } - - const { repositoryId } = match.groups; - const repository = this.scmService.getRepository(repositoryId); - - if (repository === undefined || repository.provider.rootUri === undefined) { - return resource.toString(); - } - - const folder = this.workspaceContextService.getWorkspaceFolder(repository.provider.rootUri); - const repositoryName = folder?.uri.toString() === repository.provider.rootUri.toString() ? folder.name : basename(repository.provider.rootUri); - - return `${repositoryName} (${repository.provider.label})`; - }; - - constructor( - @ISCMService private readonly scmService: ISCMService, - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, - ) { } -} - -export class SCMInputTextDocumentContribution implements IWorkbenchContribution { - constructor( - @IInstantiationService instantiationService: IInstantiationService, - @ILabelService labelService: ILabelService - ) { - labelService.registerFormatter({ - scheme: Schemas.vscode, - authority: 'scm', - formatting: instantiationService.createInstance(SCMInputTextDocumentLabelFormatter) - }); - } -} diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 6c0ceafb8ac..956e867a21f 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -10,7 +10,7 @@ import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { SCMActiveResourceContextKeyController, SCMInputTextDocumentContribution, SCMStatusController } from './activity'; +import { SCMActiveResourceContextKeyController, SCMStatusController } from './activity'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -115,9 +115,6 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(SCMInputTextDocumentContribution, LifecyclePhase.Restored); - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ id: 'scm', order: 5, diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index e809b5cb465..32a0d626d66 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1788,9 +1788,8 @@ class SCMInputWidget { } const uri = URI.from({ - scheme: Schemas.vscode, - authority: 'scm', - path: `/${input.repository.provider.contextValue}/${input.repository.provider.id}/input`, + scheme: Schemas.vscodeSourceControl, + path: `${input.repository.provider.contextValue}/${input.repository.provider.id}/input`, query }); @@ -1964,7 +1963,7 @@ class SCMInputWidget { scrollbar: { alwaysConsumeMouseWheel: false }, overflowWidgetsDomNode, renderWhitespace: 'none', - enableDropIntoEditor: true, + dropIntoEditor: { enabled: true }, accessibilitySupport }; diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index dc0023470ab..4b503feac04 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -456,7 +456,8 @@ class ReplaceActionRunner { async performReplace(element: FolderMatch | FileMatch | Match): Promise { // since multiple elements can be selected, we need to check the type of the FolderMatch/FileMatch/Match before we perform the replace. - const elementsToReplace = getElementsToOperateOnInfo(this.viewer, element, this.configurationService.getValue('search')); + const opInfo = getElementsToOperateOnInfo(this.viewer, element, this.configurationService.getValue('search')); + const elementsToReplace = opInfo.elements; await Promise.all(elementsToReplace.map(async (elem) => { const parent = elem.parent(); @@ -585,11 +586,12 @@ export class RemoveAction extends AbstractSearchAndReplaceAction { } override run(): Promise { - const elementsToRemove = getElementsToOperateOnInfo(this.viewer, this.element, this.configurationService.getValue('search')); + const opInfo = getElementsToOperateOnInfo(this.viewer, this.element, this.configurationService.getValue('search')); + const elementsToRemove = opInfo.elements; const currentBottomFocusElement = elementsToRemove[elementsToRemove.length - 1]; - const nextFocusElement = !currentBottomFocusElement || currentBottomFocusElement instanceof SearchResult || arrayContainsElementOrParent(currentBottomFocusElement, elementsToRemove) ? + const nextFocusElement = opInfo.mustReselect && (!currentBottomFocusElement || currentBottomFocusElement instanceof SearchResult || arrayContainsElementOrParent(currentBottomFocusElement, elementsToRemove)) ? this.getElementToFocusAfterRemoved(this.viewer, currentBottomFocusElement) : null; @@ -823,13 +825,15 @@ export const focusSearchListCommand: ICommandHandler = accessor => { }); }; -function getElementsToOperateOnInfo(viewer: WorkbenchObjectTree, currElement: RenderableMatch, sortConfig: ISearchConfigurationProperties): RenderableMatch[] { +function getElementsToOperateOnInfo(viewer: WorkbenchObjectTree, currElement: RenderableMatch, sortConfig: ISearchConfigurationProperties): { elements: RenderableMatch[]; mustReselect: boolean } { let elements: RenderableMatch[] = viewer.getSelection().filter((x): x is RenderableMatch => x !== null).sort((a, b) => searchComparer(a, b, sortConfig.sortOrder)); + const mustReselect = elements.includes(currElement); // this indicates whether we need to re-focus/re-select on a remove. + // if selection doesn't include multiple elements, just return current focus element. if (!(elements.length > 1 && elements.includes(currElement))) { elements = [currElement]; } - return elements; + return { elements, mustReselect }; } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index dbe5a62e3be..18a68093766 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -340,7 +340,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer processContext.set(process && !isVirtual); } this._onDidRegisterSupportedExecutions.fire(); - if (this._jsonTasksSupported && !this._tasksReconnected) { + if (this._configurationService.getValue(TaskSettingId.Reconnection) === true && this._jsonTasksSupported && !this._tasksReconnected) { this._reconnectTasks(); } } @@ -398,9 +398,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer type: 'string', description: nls.localize('runTask.type', "The contributed task type") }, - taskName: { + task: { type: 'string', - description: nls.localize('runTask.taskName', "The task's label or a term to filter by") + description: nls.localize('runTask.task', "The task's label or a term to filter by") } } } @@ -2700,7 +2700,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer })) === true; } - private async _runTaskCommand(filter?: { type?: string; taskName?: string } | string): Promise { + private async _runTaskCommand(filter?: any | { type?: string; task?: string }): Promise { if (!this._canRunCommand()) { return; } @@ -2708,8 +2708,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let typeFilter: boolean = false; if (filter && typeof filter !== 'string') { // name takes precedence - typeFilter = !filter?.taskName && !!filter?.type; - filter = filter?.taskName || filter?.type; + typeFilter = !filter?.task && !!filter?.type; + filter = filter?.task || filter?.type; } const taskIdentifier: KeyedTaskIdentifier | undefined | string = this._getTaskIdentifier(filter); @@ -2828,6 +2828,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer taskQuickPick.dispose(); return; } else { + if (!!filter) { + // filter yielded no results, so show all + this._runTaskCommand(); + } return; } } else { diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 658d86f53b2..9e62a8604dc 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -513,6 +513,12 @@ configurationRegistry.registerConfiguration({ description: nls.localize('task.showDecorations', "Shows decorations at points of interest in the terminal buffer such as the first problem found via a watch task. Note that this will only take effect for future tasks."), default: true }, + [TaskSettingId.Reconnection]: { + type: 'boolean', + description: nls.localize('task.experimental.reconnection', "On window reload, reconnect to running watch/background tasks. Note that this is experimental, so you could encounter issues."), + default: false, + tags: ['experimental'] + }, [TaskSettingId.SaveBeforeRun]: { markdownDescription: nls.localize( 'task.saveBeforeRun', diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 22c53792466..ff02f515527 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -30,14 +30,14 @@ import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IShellLaunchConfig, TerminalLocation, TerminalSettingId, WaitOnExitValue } from 'vs/platform/terminal/common/terminal'; import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; -import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IExtensionTaskSource, InMemoryTask, IShellConfiguration, IShellQuotingOptions, ITaskEvent, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, Task, TaskEvent, TaskEventKind, TaskScope, TaskSettingId, TaskSourceKind } from 'vs/workbench/contrib/tasks/common/tasks'; +import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IConfigurationProperties, IExtensionTaskSource, InMemoryTask, IPresentationOptions, IShellConfiguration, IShellQuotingOptions, ITaskEvent, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, Task, TaskEvent, TaskEventKind, TaskScope, TaskSettingId, TaskSourceKind } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; import { IResolvedVariables, IResolveSet, ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskSystemInfoResolver, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -284,6 +284,11 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } if (this._tasksToReconnect.includes(task._id)) { this._terminalForTask = terminals.find(t => t.shellLaunchConfig.attachPersistentProcess?.task?.id === task._id); + // Restore the waitOnExit value of the terminal because it may have been a function + // that cannot be persisted in the pty host + if ('command' in task && task.command.presentation && this._terminalForTask) { + this._terminalForTask.waitOnExit = getWaitOnExitValue(task.command.presentation, task.configurationProperties); + } this.run(task, resolver, trigger); } return undefined; @@ -541,35 +546,38 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { alreadyResolved = alreadyResolved ?? new Map(); const promises: Promise[] = []; if (task.configurationProperties.dependsOn) { - for (const dependency of task.configurationProperties.dependsOn) { - const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); - if (dependencyTask) { - this._adoptConfigurationForDependencyTask(dependencyTask, task); - const key = dependencyTask.getMapKey(); - let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined; - if (!promise) { - this._fireTaskEvent(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); - encounteredDependencies.add(task.getCommonTaskId()); - promise = this._executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved); - } - promises.push(promise); - if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { - const promiseResult = await promise; - if (promiseResult.exitCode === 0) { - promise = Promise.resolve(promiseResult); - } else { - promise = Promise.reject(promiseResult); - break; + if (!this._terminalForTask) { + // we already handle dependent tasks when reconnecting, don't create extras + for (const dependency of task.configurationProperties.dependsOn) { + const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); + if (dependencyTask) { + this._adoptConfigurationForDependencyTask(dependencyTask, task); + const key = dependencyTask.getMapKey(); + let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined; + if (!promise) { + this._fireTaskEvent(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); + encounteredDependencies.add(task.getCommonTaskId()); + promise = this._executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved); } + promises.push(promise); + if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { + const promiseResult = await promise; + if (promiseResult.exitCode === 0) { + promise = Promise.resolve(promiseResult); + } else { + promise = Promise.reject(promiseResult); + break; + } + } + promises.push(promise); + } else { + this._log(nls.localize('dependencyFailed', + 'Couldn\'t resolve dependent task \'{0}\' in workspace folder \'{1}\'', + Types.isString(dependency.task) ? dependency.task : JSON.stringify(dependency.task, undefined, 0), + dependency.uri.toString() + )); + this._showOutput(); } - promises.push(promise); - } else { - this._log(nls.localize('dependencyFailed', - 'Couldn\'t resolve dependent task \'{0}\' in workspace folder \'{1}\'', - Types.isString(dependency.task) ? dependency.task : JSON.stringify(dependency.task, undefined, 0), - dependency.uri.toString() - )); - this._showOutput(); } } } @@ -951,7 +959,6 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } - this._fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode ?? undefined)); for (let i = 0; i < eventCounter; i++) { @@ -1065,7 +1072,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return needsFolderQualification ? task.getQualifiedLabel() : (task.configurationProperties.name || ''); } - private async _createShellLaunchConfig(task: CustomTask | ContributedTask, workspaceFolder: IWorkspaceFolder | undefined, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: boolean | string | ((exitCode: number) => string)): Promise { + private async _createShellLaunchConfig(task: CustomTask | ContributedTask, workspaceFolder: IWorkspaceFolder | undefined, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: WaitOnExitValue): Promise { let shellLaunchConfig: IShellLaunchConfig; const isShellCommand = task.command.runtime === RuntimeType.Shell; const needsFolderQualification = this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; @@ -1338,24 +1345,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { const options = await this._resolveOptions(resolver, task.command.options); const presentationOptions = task.command.presentation; - let waitOnExit: boolean | string | ((exitCode: number) => string) = false; if (!presentationOptions) { throw new Error('Task presentation options should not be undefined here.'); } - - if ((presentationOptions.close === undefined) || (presentationOptions.close === false)) { - if ((presentationOptions.reveal !== RevealKind.Never) || !task.configurationProperties.isBackground || (presentationOptions.close === false)) { - if (presentationOptions.panel === PanelKind.New) { - waitOnExit = taskShellIntegrationWaitOnExitSequence(nls.localize('closeTerminal', 'Press any key to close the terminal.')); - } else if (presentationOptions.showReuseMessage) { - waitOnExit = taskShellIntegrationWaitOnExitSequence(nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.')); - } else { - waitOnExit = true; - } - } - } else { - waitOnExit = !presentationOptions.close; - } + const waitOnExit = getWaitOnExitValue(presentationOptions, task.configurationProperties); let command: CommandString | undefined; let args: CommandString[] | undefined; @@ -1384,7 +1377,6 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return [undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)]; } } - const prefersSameTerminal = presentationOptions.panel === PanelKind.Dedicated; const allowsSharedTerminal = presentationOptions.panel === PanelKind.Shared; const group = presentationOptions.group; @@ -1787,6 +1779,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } } +function getWaitOnExitValue(presentationOptions: IPresentationOptions, configurationProperties: IConfigurationProperties) { + if ((presentationOptions.close === undefined) || (presentationOptions.close === false)) { + if ((presentationOptions.reveal !== RevealKind.Never) || !configurationProperties.isBackground || (presentationOptions.close === false)) { + if (presentationOptions.panel === PanelKind.New) { + return taskShellIntegrationWaitOnExitSequence(nls.localize('closeTerminal', 'Press any key to close the terminal.')); + } else if (presentationOptions.showReuseMessage) { + return taskShellIntegrationWaitOnExitSequence(nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.')); + } else { + return true; + } + } + } + return !presentationOptions.close; +} + function taskShellIntegrationWaitOnExitSequence(message: string): (exitCode: number) => string { return (exitCode) => { return `${VSCodeSequence(VSCodeOscPt.CommandFinished, exitCode.toString())}${message}`; diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 2033ec632d9..cfa357558d2 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -1201,7 +1201,8 @@ export const enum TaskSettingId { QuickOpenDetail = 'task.quickOpen.detail', QuickOpenSkip = 'task.quickOpen.skip', QuickOpenShowAll = 'task.quickOpen.showAll', - AllowAutomaticTasks = 'task.allowAutomaticTasks' + AllowAutomaticTasks = 'task.allowAutomaticTasks', + Reconnection = 'task.experimental.reconnection' } export const enum TasksSchemaProperties { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 45871689f60..ed6ba54d0eb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -11,7 +11,7 @@ import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TerminalType, TitleEventSource, WaitOnExitValue } from 'vs/platform/terminal/common/terminal'; import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; @@ -257,6 +257,11 @@ interface ITerminalEditorInputObject { readonly icon: TerminalIcon | undefined; readonly color: string | undefined; readonly hasChildProcesses?: boolean; + readonly task?: { label: string; id: string; lastTask: string; group?: string; waitOnExit?: WaitOnExitValue }; + readonly type?: TerminalType; + readonly isFeatureTerminal?: boolean; + readonly hideFromUser?: boolean; + readonly reconnectionOwner?: string; } export interface ISerializedTerminalEditorInput extends ITerminalEditorInputObject { @@ -513,6 +518,12 @@ export interface ITerminalInstance { */ readonly hasFocus: boolean; + /** + * Get or set the behavior of the terminal when it closes. This was indented only to be called + * after reconnecting to a terminal. + */ + waitOnExit: WaitOnExitValue | undefined; + /** * An event that fires when the terminal instance's title changes. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts index 2a8abde35cb..853b4eb98e7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts @@ -43,7 +43,11 @@ export class TerminalInputSerializer implements IEditorSerializer { icon: instance.icon, color: instance.color, resource: instance.resource.toString(), - hasChildProcesses: instance.hasChildProcesses + hasChildProcesses: instance.hasChildProcesses, + task: instance.shellLaunchConfig.task, + type: instance.shellLaunchConfig.type, + isFeatureTerminal: instance.shellLaunchConfig.isFeatureTerminal, + hideFromUser: instance.shellLaunchConfig.hideFromUser, }; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 09736ecd0b0..fde509c864f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -87,6 +87,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; +import { TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks'; const enum Constants { /** @@ -226,6 +227,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { xterm?: XtermTerminal; disableLayout: boolean = false; + get waitOnExit(): ITerminalInstance['waitOnExit'] { return this._shellLaunchConfig.attachPersistentProcess?.waitOnExit || this._shellLaunchConfig.waitOnExit; } + set waitOnExit(value: ITerminalInstance['waitOnExit']) { + this._shellLaunchConfig.waitOnExit = value; + } + get target(): TerminalLocation | undefined { return this._target; } set target(value: TerminalLocation | undefined) { if (this.xterm) { @@ -303,12 +309,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get description(): string | undefined { if (this._description) { return this._description; - } else if (this._shellLaunchConfig.type) { - if (this._shellLaunchConfig.type === 'Task') { + } + const type = this.shellLaunchConfig.attachPersistentProcess?.type || this.shellLaunchConfig.type; + if (type) { + if (type === 'Task') { return nls.localize('terminalTypeTask', "Task"); - } else { - return nls.localize('terminalTypeLocal', "Local"); } + return nls.localize('terminalTypeLocal', "Local"); } return undefined; } @@ -407,6 +414,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // the resource is already set when it's been moved from another window this._resource = resource || getTerminalUri(this._workspaceContextService.getWorkspace().id, this.instanceId, this.title); + if (this._shellLaunchConfig.attachPersistentProcess?.hideFromUser) { + this._shellLaunchConfig.hideFromUser = this._shellLaunchConfig.attachPersistentProcess.hideFromUser; + } + + if (this._shellLaunchConfig.attachPersistentProcess?.isFeatureTerminal) { + this._shellLaunchConfig.isFeatureTerminal = this._shellLaunchConfig.attachPersistentProcess.isFeatureTerminal; + } + + if (this._shellLaunchConfig.attachPersistentProcess?.type) { + this._shellLaunchConfig.type = this._shellLaunchConfig.attachPersistentProcess.type; + } + if (this.shellLaunchConfig.cwd) { const cwdUri = typeof this._shellLaunchConfig.cwd === 'string' ? URI.from({ scheme: Schemas.file, @@ -537,7 +556,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._labelComputer?.refreshLabel(); } })); - this._workspaceContextService.onDidChangeWorkspaceFolders(() => this._labelComputer?.refreshLabel()); + this.addDisposable(this._workspaceContextService.onDidChangeWorkspaceFolders(() => this._labelComputer?.refreshLabel())); // Clear out initial data events after 10 seconds, hopefully extension hosts are up and // running at that point. @@ -677,7 +696,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._shutdownPersistentProcessId = shutdownPersistentProcessId; } get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId ?? this._shutdownPersistentProcessId; } - get shouldPersist(): boolean { return (this._processManager.shouldPersist || this._shutdownPersistentProcessId !== undefined) && !this.shellLaunchConfig.isTransient; } + get shouldPersist(): boolean { return (this._processManager.shouldPersist || this._shutdownPersistentProcessId !== undefined) && !this.shellLaunchConfig.isTransient && (!this.reconnectionOwner || this._configurationService.getValue(TaskSettingId.Reconnection) === true); } /** * Create xterm.js instance and attach data listeners. @@ -760,11 +779,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._loadTypeAheadAddon(xterm); - this._configurationService.onDidChangeConfiguration(e => { + this.addDisposable(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(TerminalSettingId.LocalEchoEnabled)) { this._loadTypeAheadAddon(xterm); } - }); + })); this._pathService.userHome().then(userHome => { this._userHome = userHome.fsPath; @@ -1764,18 +1783,19 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Only trigger wait on exit when the exit was *not* triggered by the // user (via the `workbench.action.terminal.kill` command). - if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KilledByUser) { + const waitOnExit = this.waitOnExit; + if (waitOnExit && this._processManager.processState !== ProcessState.KilledByUser) { this._xtermReadyPromise.then(xterm => { if (exitMessage) { xterm.raw.write(formatMessageForTerminal(exitMessage)); } - switch (typeof this._shellLaunchConfig.waitOnExit) { + switch (typeof waitOnExit) { case 'string': - xterm.raw.write(formatMessageForTerminal(this._shellLaunchConfig.waitOnExit, { excludeLeadingNewLine: true })); + xterm.raw.write(formatMessageForTerminal(waitOnExit, { excludeLeadingNewLine: true })); break; case 'function': if (this.exitCode !== undefined) { - xterm.raw.write(formatMessageForTerminal(this._shellLaunchConfig.waitOnExit(this.exitCode), { excludeLeadingNewLine: true })); + xterm.raw.write(formatMessageForTerminal(waitOnExit(this.exitCode), { excludeLeadingNewLine: true })); } break; } @@ -2367,12 +2387,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } // Recreate the process if the terminal has not yet been interacted with and it's not a - // special terminal (eg. task, extension terminal) + // special terminal (eg. extension terminal) if ( info.requiresAction && this._configHelper.config.environmentChangesRelaunch && !this._processManager.hasWrittenData && - (this.reconnectionOwner || !this._shellLaunchConfig.isFeatureTerminal) && + (!this._shellLaunchConfig.isFeatureTerminal || (this.reconnectionOwner && this._configurationService.getValue(TaskSettingId.Reconnection) === true)) && !this._shellLaunchConfig.customPtyImplementation && !this._shellLaunchConfig.isExtensionOwnedTerminal && !this._shellLaunchConfig.attachPersistentProcess @@ -2732,14 +2752,15 @@ export class TerminalLabelComputer extends Disposable { labelType: TerminalLabelType, reset?: boolean ) { + const type = this._instance.shellLaunchConfig.attachPersistentProcess?.type || this._instance.shellLaunchConfig.type; const templateProperties: ITerminalLabelTemplateProperties = { cwd: this._instance.cwd || this._instance.initialCwd || '', cwdFolder: '', workspaceFolder: this._instance.workspaceFolder ? path.basename(this._instance.workspaceFolder.uri.fsPath) : undefined, - local: this._instance.shellLaunchConfig.type === 'Local' ? this._instance.shellLaunchConfig.type : undefined, + local: type === 'Local' ? type : undefined, process: this._instance.processName, sequence: this._instance.sequence, - task: this._instance.shellLaunchConfig.type === 'Task' ? this._instance.shellLaunchConfig.type : undefined, + task: type === 'Task' ? type : undefined, fixedDimensions: this._instance.fixedCols ? (this._instance.fixedRows ? `\u2194${this._instance.fixedCols} \u2195${this._instance.fixedRows}` : `\u2194${this._instance.fixedCols}`) : (this._instance.fixedRows ? `\u2195${this._instance.fixedRows}` : ''), diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 901505cb26a..a82ae60c449 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -36,6 +36,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -245,8 +246,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce // this is a copy of what the merged environment collection is on the remote side const env = await this._resolveEnvironment(backend, variableResolver, shellLaunchConfig); - - const shouldPersist = (!!shellLaunchConfig.reconnectionOwner || !shellLaunchConfig.isFeatureTerminal) && this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isTransient; + const shouldPersist = ((this._configurationService.getValue(TaskSettingId.Reconnection) && shellLaunchConfig.reconnectionOwner) || !shellLaunchConfig.isFeatureTerminal) && this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isTransient; if (shellLaunchConfig.attachPersistentProcess) { const result = await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id); if (result) { @@ -318,7 +318,6 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } this._process = newProcess; - this._setProcessState(ProcessState.Launching); // Add any capabilities inherit to the backend @@ -441,7 +440,6 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce remoteAuthority: undefined, os: OS }); - const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file); const initialCwd = await terminalEnvironment.getCwd( @@ -462,7 +460,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce windowsEnableConpty: this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled, environmentVariableCollections: this._extEnvironmentVariableCollection ? serializeEnvironmentVariableCollections(this._extEnvironmentVariableCollection.collections) : undefined }; - const shouldPersist = this._configHelper.config.enablePersistentSessions && (!!this.reconnectionOwner || !shellLaunchConfig.isFeatureTerminal); + const shouldPersist = ((this._configurationService.getValue(TaskSettingId.Reconnection) && shellLaunchConfig.reconnectionOwner) || !shellLaunchConfig.isFeatureTerminal) && this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isTransient; return await backend.createProcess(shellLaunchConfig, initialCwd, cols, rows, this._configHelper.config.unicodeVersion, env, options, shouldPersist); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index f315f11cd30..1d504f3334c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -175,7 +175,6 @@ export class TerminalService implements ITerminalService { // we update detected profiles when an instance is created so that, // for example, we detect if you've installed a pwsh this.onDidCreateInstance(() => this._terminalProfileService.refreshAvailableProfiles()); - this._forwardInstanceHostEvents(this._terminalGroupService); this._forwardInstanceHostEvents(this._terminalEditorService); this._terminalGroupService.onDidChangeActiveGroup(this._onDidChangeActiveGroup.fire, this._onDidChangeActiveGroup); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index d262afd51fb..2b1b063de2d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -458,12 +458,14 @@ class TerminalTabsRenderer implements IListRenderer this._contextMenuVisible = false)); this._hoverDelayer = this._register(new Delayer(this._configurationService.getValue('workbench.hover.delay'))); - this._configurationService.onDidChangeConfiguration(e => { + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationIcon) || e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationIconSuccess) || e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationIconError)) { @@ -92,8 +92,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } this._updateDecorationVisibility(); } - }); - this._themeService.onDidColorThemeChange(() => this._refreshStyles(true)); + })); + this._register(this._themeService.onDidColorThemeChange(() => this._refreshStyles(true))); this._updateDecorationVisibility(); this._register(this._capabilities.onDidAddCapability(c => { if (c === TerminalCapability.CommandDetection) { @@ -442,7 +442,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { const labelAbout = localize("terminal.learnShellIntegration", 'Learn About Shell Integration'); actions.push({ class: undefined, tooltip: labelAbout, dispose: () => { }, id: 'terminal.learnShellIntegration', label: labelAbout, enabled: true, - run: () => this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration') + run: () => this._openerService.open('https://code.visualstudio.com/docs/terminal/shell-integration') }); return actions; } diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 556f7f4e65d..53579678c41 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -249,7 +249,7 @@ export class ConfigureTestProfilesAction extends Action2 { constructor() { super({ id: TestCommandId.ConfigureTestProfilesAction, - title: localize('testing.configureProfile', 'Configure Test Profiles'), + title: { value: localize('testing.configureProfile', 'Configure Test Profiles'), original: 'Configure Test Profiles' }, icon: icons.testingUpdateProfiles, f1: true, category, diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 718807b102e..d42faa01aa9 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -815,11 +815,39 @@ class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoratio allActions.push(new Action('testing.gutter.debugAll', localize('debug all test', 'Debug All Tests'), undefined, undefined, () => this.defaultDebug())); } + const testItems = this.tests.map(testItem => ({ + currentLabel: testItem.test.item.label, + testItem, + parent: testItem.test.parent, + })); + + const getLabelConflicts = (tests: typeof testItems) => { + const labelCount = new Map(); + for (const test of tests) { + labelCount.set(test.currentLabel, (labelCount.get(test.currentLabel) || 0) + 1); + } + + return tests.filter(e => labelCount.get(e.currentLabel)! > 1); + }; + + let conflicts, hasParent = true; + while ((conflicts = getLabelConflicts(testItems)).length && hasParent) { + for (const conflict of conflicts) { + if (conflict.parent) { + const parent = this.testService.collection.getNodeById(conflict.parent); + conflict.currentLabel = parent?.item.label + ' > ' + conflict.currentLabel; + conflict.parent = parent?.parent ? parent.parent : null; + } else { + hasParent = false; + } + } + } + const disposable = new DisposableStore(); - const testSubmenus = this.tests.map(({ test, resultItem }) => { - const actions = this.getTestContextMenuActions(test, resultItem); + const testSubmenus = testItems.map(({ currentLabel, testItem }) => { + const actions = this.getTestContextMenuActions(testItem.test, testItem.resultItem); disposable.add(actions); - return new SubmenuAction(test.item.extId, stripIcons(test.item.label), actions.object); + return new SubmenuAction(testItem.test.item.extId, stripIcons(currentLabel), actions.object); }); return { object: Separator.join(allActions, testSubmenus), dispose: () => disposable.dispose() }; diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 2281441286a..a326f747bc5 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -491,6 +491,7 @@ export class TestingExplorerViewModel extends Disposable { keyboardNavigationLabelProvider: instantiationService.createInstance(TreeKeyboardNavigationLabelProvider), accessibilityProvider: instantiationService.createInstance(ListAccessibilityProvider), filter: this.filter, + findWidgetEnabled: false }) as WorkbenchObjectTree; this._register(this.tree.onDidChangeCollapseState(evt => { diff --git a/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts b/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts index 69b323858b3..e8f1733a68f 100644 --- a/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts +++ b/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts @@ -129,6 +129,7 @@ export class TestingProgressUiService extends Disposable implements ITestingProg if (!this.windowProg.value) { this.windowProg.value = this.instantiaionService.createInstance(UnmanagedProgress, { location: ProgressLocation.Window, + type: 'loading' }); this.testViewProg.value = this.instantiaionService.createInstance(UnmanagedProgress, { location: Testing.ViewletId, diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index f1d27222b50..62e3de604fe 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -375,6 +375,7 @@ registerAction2(class ImportProfileAction extends Action2 { const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); const dialogService = accessor.get(IDialogService); const contextKeyService = accessor.get(IContextKeyService); + const notificationService = accessor.get(INotificationService); const isSettingProfilesEnabled = contextKeyService.contextMatchesRules(PROFILES_ENABLEMENT_CONTEXT); @@ -401,14 +402,18 @@ registerAction2(class ImportProfileAction extends Action2 { quickPick.matchOnLabel = false; quickPick.matchOnDescription = false; disposables.add(quickPick.onDidAccept(async () => { - quickPick.hide(); - const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService); - if (profile) { - if (isSettingProfilesEnabled) { - await userDataProfileImportExportService.importProfile(profile); - } else { - await userDataProfileImportExportService.setProfile(profile); + try { + quickPick.hide(); + const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService); + if (profile) { + if (isSettingProfilesEnabled) { + await userDataProfileImportExportService.importProfile(profile); + } else { + await userDataProfileImportExportService.setProfile(profile); + } } + } catch (error) { + notificationService.error(error); } })); disposables.add(quickPick.onDidHide(() => disposables.dispose())); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index d201a99a8a6..55f61b3c685 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -57,8 +57,10 @@ import { IUserDataInitializationService } from 'vs/workbench/services/userData/b import { MarkdownString } from 'vs/base/common/htmlContent'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ctxIsMergeEditor, ctxMergeBaseUri } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -728,12 +730,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo for (const conflict of conflicts) { const remoteResourceName = localize({ key: 'remoteResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); const localResourceName = localize('localResourceName', "{0} (Local)", basename(conflict.remoteResource)); - await this.editorService.openEditor({ - input1: { resource: conflict.remoteResource, label: localize('Theirs', 'Theirs'), description: remoteResourceName }, - input2: { resource: conflict.localResource, label: localize('Yours', 'Yours'), description: localResourceName }, - base: { resource: conflict.baseResource }, - result: { resource: conflict.previewResource } - }); + const input = this.instantiationService.createInstance( + MergeEditorInput, + conflict.baseResource, + { title: localize('Yours', 'Yours'), description: localResourceName, detail: undefined, uri: conflict.localResource }, + { title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, + conflict.previewResource, + ); + await this.editorService.openEditor(input, { override: EditorResolution.DISABLED }); } } diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index 8bba582ed17..d0ce103adc8 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -1070,8 +1070,10 @@ } try { - context = { ...JSON.parse(el.getAttribute('data-vscode-context')), ...context }; - } catch {} + context = { ...JSON.parse(el.dataset.vscodeContext), ...context }; + } catch (e) { + console.error(`Error parsing 'data-vscode-context' as json`, el, e) + } el = el.parentElement; } diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 52276ac415c..6c00e8b5da2 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -5,7 +5,7 @@ + content="default-src 'none'; script-src 'sha256-JpX/ganPoxpavjxWCz9DUZgwVZ59o2lwSYTQrziPsdU=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> ('webviewEditorService'); @@ -94,13 +94,11 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { constructor( - id: string, - viewType: string, - name: string, + init: WebviewInputInitInfo, webview: IOverlayWebview, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, ) { - super(id, viewType, name, webview, _webviewWorkbenchService.iconManager); + super(init, webview, _webviewWorkbenchService.iconManager); } override dispose() { @@ -244,7 +242,7 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc showOptions: ICreateWebViewShowOptions, ): WebviewInput { const webview = this._webviewService.createWebviewOverlay(webviewInitInfo); - const webviewInput = this._instantiationService.createInstance(WebviewInput, webviewInitInfo.id, viewType, title, webview, this.iconManager); + const webviewInput = this._instantiationService.createInstance(WebviewInput, { id: webviewInitInfo.id, viewType, name: title, providedId: webviewInitInfo.providedId }, webview, this.iconManager); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus, @@ -295,7 +293,7 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc const webview = this._webviewService.createWebviewOverlay(options.webviewInitInfo); webview.state = options.state; - const webviewInput = this._instantiationService.createInstance(LazilyResolvedWebviewEditorInput, options.webviewInitInfo.id, options.viewType, options.title, webview); + const webviewInput = this._instantiationService.createInstance(LazilyResolvedWebviewEditorInput, { id: options.webviewInitInfo.id, viewType: options.viewType, providedId: options.webviewInitInfo.providedId, name: options.title }, webview); webviewInput.iconPath = options.iconPath; if (typeof options.group === 'number') { diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 1dd0c84873b..6559a5f52b7 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -242,6 +242,7 @@ import { ModifierKeyEmitter } from 'vs/base/browser/dom'; type: 'boolean', description: localize('experimentalUseSandbox', "Experimental: When enabled, the window will have sandbox mode enabled via Electron API."), default: false, + 'scope': ConfigurationScope.APPLICATION, ignoreSync: true }, } diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index f6f5b60e313..4720f73a32a 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -207,22 +207,7 @@ export class TitlebarPart extends BrowserTitleBarPart { } const zoomFactor = getZoomFactor(); - const boundingRect = this.rootContainer.getBoundingClientRect(); - const eventPosition = { x, y }; - const relativeCoordinates = { x, y }; - // When comparing the coordinates with the title bar, account for zoom level if not using counter zoom. - if (!this.useCounterZoom) { - relativeCoordinates.x /= zoomFactor; - relativeCoordinates.y /= zoomFactor; - } - - // Don't trigger the menu if the click is not over the title bar - if (relativeCoordinates.x < boundingRect.left || relativeCoordinates.x > boundingRect.right || - relativeCoordinates.y < boundingRect.top || relativeCoordinates.y > boundingRect.bottom) { - return; - } - - this.onContextMenu(new MouseEvent('mouseup', { clientX: eventPosition.x / zoomFactor, clientY: eventPosition.y / zoomFactor }), MenuId.TitleBarContext); + this.onContextMenu(new MouseEvent('mouseup', { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext); })); } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 54779255d79..f044363676a 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -65,7 +65,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; export class NativeWindow extends Disposable { @@ -116,8 +115,7 @@ export class NativeWindow extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, @IProgressService private readonly progressService: IProgressService, - @ILabelService private readonly labelService: ILabelService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @ILabelService private readonly labelService: ILabelService ) { super(); @@ -131,15 +129,7 @@ export class NativeWindow extends Disposable { this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true))); // React to editor input changes - const appRootUri = URI.file(this.environmentService.appRoot); - this._register(this.editorService.onDidActiveEditorChange(() => { - - // Touchbar - this.updateTouchbarMenu(); - - // Potential data loss - this.notifyOnAppRootEditors(appRootUri); - })); + this._register(this.editorService.onDidActiveEditorChange(() => this.updateTouchbarMenu())); // prevent opening a real URL inside the window for (const event of [EventType.DRAG_OVER, EventType.DROP]) { @@ -805,46 +795,6 @@ export class NativeWindow extends Disposable { } } - private notifyOnAppRootEditors(appRootUri: URI): void { - const resourceUri = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.BOTH }); - const isResourceAppRootedFn = (uri: URI): boolean => this.uriIdentityService.extUri.isEqualOrParent(uri, appRootUri); - let isResourceAppRooted = false; - if (URI.isUri(resourceUri)) { - if (isResourceAppRootedFn(resourceUri)) { - isResourceAppRooted = true; - } - } else if (resourceUri) { - if (resourceUri.primary && isResourceAppRootedFn(resourceUri.primary)) { - isResourceAppRooted = true; - } else if (resourceUri.secondary && isResourceAppRootedFn(resourceUri.secondary)) { - isResourceAppRooted = true; - } - } - - // It is dangerous to edit files in the installation directory of Code because - // an update will remove all files and replace them with the new version. - // As such, we notify the user whenever an editor opens that is located somewhere - // in the installation directory. - // https://github.com/microsoft/vscode/issues/138815 - - if (isResourceAppRooted) { - this.notificationService.prompt( - Severity.Warning, - localize('notifyOnAppRootEditors', "Files within the installation folder of '{0}' ({1}) will be OVERWRITTEN or DELETED IRREVERSIBLY without warning during a future update.", this.productService.nameShort, this.environmentService.appRoot), - [{ - label: localize('understood', 'Understood'), - run: async () => { - // Nothing to do - } - }], - { - neverShowAgain: { id: 'window.notifyOnAppRootEditors', isSecondary: true }, - sticky: true - } - ); - } - } - private onAddFoldersRequest(request: IAddFoldersRequest): void { // Buffer all pending requests @@ -912,7 +862,7 @@ export class NativeWindow extends Disposable { input2: { resource: resources[1].resource }, base: { resource: resources[2].resource }, result: { resource: resources[3].resource }, - options: { pinned: true } + options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready }; editors.push(mergeEditor); } else if (diffMode && isResourceEditorInput(resources[0]) && isResourceEditorInput(resources[1])) { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 3dc4ea6419e..a1245856684 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -717,7 +717,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat if (e.preserveData) { await Promise.all([ this.copyProfileSettings(e.previous.settingsResource, e.profile.settingsResource), - this.fileService.copy(e.previous.tasksResource, e.profile.tasksResource) + this.copyProfileTasks(e.previous.tasksResource, e.profile.tasksResource) ]); } const promises: Promise[] = []; @@ -751,6 +751,12 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat await this.fileService.writeFile(to, VSBuffer.fromString(toContent)); } + private async copyProfileTasks(from: URI, to: URI): Promise { + if (await this.fileService.exists(from)) { + await this.fileService.copy(from, to); + } + } + private onDefaultConfigurationChanged(configurationModel: ConfigurationModel, properties?: string[]): void { if (this.workspace) { const previousData = this._configuration.toData(); @@ -906,12 +912,12 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private async onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): Promise { const [folderConfiguration] = await this.loadFolderConfigurations([folder]); const previous = { data: this._configuration.toData(), workspace: this.workspace }; - const folderConfiguraitonChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); + const folderConfigurationChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); if (this.getWorkbenchState() === WorkbenchState.FOLDER) { const workspaceConfigurationChange = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); - this.triggerConfigurationChange(mergeChanges(folderConfiguraitonChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE); + this.triggerConfigurationChange(mergeChanges(folderConfigurationChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE); } else { - this.triggerConfigurationChange(folderConfiguraitonChange, previous, ConfigurationTarget.WORKSPACE_FOLDER); + this.triggerConfigurationChange(folderConfigurationChange, previous, ConfigurationTarget.WORKSPACE_FOLDER); } this.updateRestrictedSettings(); } diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts index c00b46869c6..ec9fc2b563b 100644 --- a/src/vs/workbench/services/editor/browser/editorResolverService.ts +++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts @@ -51,8 +51,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver private static readonly conflictingDefaultsStorageID = 'editorOverrideService.conflictingDefaults'; // Data Stores - private _editors: Map> = new Map>(); - private _flattenedEditors: Map = new Map(); + private _editors: Map = new Map(); private cache: Set | undefined; constructor( @@ -236,29 +235,18 @@ export class EditorResolverService extends Disposable implements IEditorResolver ): IDisposable { let registeredEditor = this._editors.get(globPattern); if (registeredEditor === undefined) { - registeredEditor = new Map(); + registeredEditor = []; this._editors.set(globPattern, registeredEditor); } - - let editorsWithId = registeredEditor.get(editorInfo.id); - if (editorsWithId === undefined) { - editorsWithId = []; - } - const remove = insert(editorsWithId, { + const remove = insert(registeredEditor, { globPattern, editorInfo, options, editorFactoryObject }); - registeredEditor.set(editorInfo.id, editorsWithId); - this._flattenedEditors = this._flattenEditorsMap(); this._onDidChangeEditorRegistrations.fire(); return toDisposable(() => { remove(); - if (editorsWithId && editorsWithId.length === 0) { - registeredEditor?.delete(editorInfo.id); - } - this._flattenedEditors = this._flattenEditorsMap(); this._onDidChangeEditorRegistrations.fire(); }); } @@ -293,43 +281,11 @@ export class EditorResolverService extends Disposable implements IEditorResolver return associations; } - /** - * Given the nested nature of the editors map, we should merge factories of the same glob and id to make it flat - */ - private _flattenEditorsMap() { - const editors = new Map(); - for (const [glob, value] of this._editors) { - const registeredEditors: RegisteredEditors = []; - for (const editors of value.values()) { - let registeredEditor: RegisteredEditor | undefined = undefined; - // Merge all editors with the same id and glob pattern together - for (const editor of editors) { - if (!registeredEditor) { - registeredEditor = { - editorInfo: editor.editorInfo, - globPattern: editor.globPattern, - options: {}, - editorFactoryObject: {} - }; - } - // Merge options and factories - registeredEditor.options = { ...registeredEditor.options, ...editor.options }; - registeredEditor.editorFactoryObject = { ...registeredEditor.editorFactoryObject, ...editor.editorFactoryObject }; - } - if (registeredEditor) { - registeredEditors.push(registeredEditor); - } - } - editors.set(glob, registeredEditors); - } - return editors; - } - /** * Returns all editors as an array. Possible to contain duplicates */ private get _registeredEditors(): RegisteredEditors { - return flatten(Array.from(this._flattenedEditors.values())); + return flatten(Array.from(this._editors.values())); } updateUserAssociations(globPattern: string, editorID: string): void { @@ -350,7 +306,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver const userSettings = this.getAssociationsForResource(resource); const matchingEditors: RegisteredEditor[] = []; // Then all glob patterns - for (const [key, editors] of this._flattenedEditors) { + for (const [key, editors] of this._editors) { for (const editor of editors) { const foundInSettings = userSettings.find(setting => setting.viewType === editor.editorInfo.id); if ((foundInSettings && editor.editorInfo.priority !== RegisteredEditorPriority.exclusive) || globMatchesResource(key, resource)) { @@ -490,11 +446,6 @@ export class EditorResolverService extends Disposable implements IEditorResolver } } - // If no factory is above, return flow back to caller letting them know we could not resolve it - if (!selectedEditor.editorFactoryObject.createEditorInput) { - return; - } - // Respect options passed back const inputWithOptions = await selectedEditor.editorFactoryObject.createEditorInput(editor, group); options = inputWithOptions.options ?? options; @@ -788,7 +739,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver const cacheStorage: Set = new Set(); // Store just the relative pattern pieces without any path info - for (const [globPattern, contribPoint] of this._flattenedEditors) { + for (const [globPattern, contribPoint] of this._editors) { const nonOptional = !!contribPoint.find(c => c.editorInfo.priority !== RegisteredEditorPriority.option && c.editorInfo.id !== DEFAULT_EDITOR_ASSOCIATION.id); // Don't keep a cache of the optional ones as those wouldn't be opened on start anyways if (!nonOptional) { diff --git a/src/vs/workbench/services/editor/common/editorResolverService.ts b/src/vs/workbench/services/editor/common/editorResolverService.ts index 9de7b992657..b4e47e8d4f2 100644 --- a/src/vs/workbench/services/editor/common/editorResolverService.ts +++ b/src/vs/workbench/services/editor/common/editorResolverService.ts @@ -19,7 +19,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorInputWithOptions, EditorInputWithOptionsAndGroup, IResourceDiffEditorInput, IResourceMergeEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { PreferredGroup } from 'vs/workbench/services/editor/common/editorService'; -import { AtLeastOne } from 'vs/base/common/types'; export const IEditorResolverService = createDecorator('editorResolverService'); @@ -110,15 +109,13 @@ export type DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEdit export type MergeEditorInputFactoryFunction = (mergeEditorInput: IResourceMergeEditorInput, group: IEditorGroup) => EditorInputFactoryResult; -type EditorInputFactories = { - createEditorInput?: EditorInputFactoryFunction; +export type EditorInputFactoryObject = { + createEditorInput: EditorInputFactoryFunction; createUntitledEditorInput?: UntitledEditorInputFactoryFunction; createDiffEditorInput?: DiffEditorInputFactoryFunction; createMergeEditorInput?: MergeEditorInputFactoryFunction; }; -export type EditorInputFactoryObject = AtLeastOne; - export interface IEditorResolverService { readonly _serviceBrand: undefined; /** @@ -141,8 +138,7 @@ export interface IEditorResolverService { readonly onDidChangeEditorRegistrations: Event; /** - * Registers a specific editor. Editors with the same glob pattern and ID will be grouped together by the resolver. - * This allows for registration of the factories in different locations + * Registers a specific editor. * @param globPattern The glob pattern for this registration * @param editorInfo Information about the registration * @param options Specific options which apply to this registration diff --git a/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts b/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts index 2b3a585ed5c..5bfe40ad314 100644 --- a/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts @@ -382,67 +382,4 @@ suite('EditorResolverService', () => { assert.strictEqual(service.getEditors().length, editors.length); assert.strictEqual(service.getEditors().some(editor => editor.id === 'TEST_EDITOR'), false); }); - - test('Multiple registrations to same glob and id #155859', async () => { - const [part, service, accessor] = await createEditorResolverService(); - const testEditorInfo = { - id: 'TEST_EDITOR', - label: 'Test Editor Label', - detail: 'Test Editor Details', - priority: RegisteredEditorPriority.default - }; - const registeredSingleEditor = service.registerEditor('*.test', - testEditorInfo, - {}, - { - createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }) - } - ); - - const registeredDiffEditor = service.registerEditor('*.test', - testEditorInfo, - {}, - { - createDiffEditorInput: ({ modified, original, options }, group) => ({ - editor: accessor.instantiationService.createInstance( - DiffEditorInput, - 'name', - 'description', - new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), - new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), - undefined) - }) - } - ); - - // Resolve a diff - let resultingResolution = await service.resolveEditor({ - original: { resource: URI.file('my://resource-basics.test') }, - modified: { resource: URI.file('my://resource-basics.test') } - }, part.activeGroup); - assert.ok(resultingResolution); - assert.notStrictEqual(typeof resultingResolution, 'number'); - if (resultingResolution !== ResolvedStatus.ABORT && resultingResolution !== ResolvedStatus.NONE) { - assert.strictEqual(resultingResolution.editor.typeId, 'workbench.editors.diffEditorInput'); - resultingResolution.editor.dispose(); - } else { - assert.fail(); - } - - // Remove diff registration - registeredDiffEditor.dispose(); - - // Resolve a diff again, expected failure - resultingResolution = await service.resolveEditor({ - original: { resource: URI.file('my://resource-basics.test') }, - modified: { resource: URI.file('my://resource-basics.test') } - }, part.activeGroup); - assert.ok(resultingResolution); - assert.strictEqual(typeof resultingResolution, 'number'); - if (resultingResolution !== ResolvedStatus.NONE) { - assert.fail(); - } - - registeredSingleEditor.dispose(); - }); }); diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index bc5e140d42a..61275010652 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -331,7 +331,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten const missingExtensions = extensions.filter(({ id }) => !galleryExtensionsMap.has(id.toLowerCase())); if (missingExtensions.length) { - this.logService.info('Skipping the additional builtin extensions because their compatible versions are not foud.', missingExtensions); + this.logService.info('Skipping the additional builtin extensions because their compatible versions are not found.', missingExtensions); } const webExtensions: IWebExtension[] = []; diff --git a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts index 34cb13522aa..ee051e3d1f1 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts @@ -413,6 +413,32 @@ export abstract class ElectronExtensionService extends AbstractExtensionService throw new Error(`Cannot get canonical URI because no extension is installed to resolve ${getRemoteAuthorityPrefix(remoteAuthority)}`); } + private async _resolveAuthorityInitial(remoteAuthority: string): Promise { + const MAX_ATTEMPTS = 5; + + for (let attempt = 1; ; attempt++) { + const sw = StopWatch.create(false); + this._logService.info(`[attempt ${attempt}] Invoking resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)})`); + try { + const resolverResult = await this._resolveAuthority(remoteAuthority); + this._logService.info(`[attempt ${attempt}] resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${resolverResult.authority.host}:${resolverResult.authority.port}' after ${sw.elapsed()} ms`); + return resolverResult; + } catch (err) { + this._logService.error(`[attempt ${attempt}] resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed()} ms`, err); + + if (RemoteAuthorityResolverError.isNoResolverFound(err)) { + // There is no point in retrying if there is no resolver found + throw err; + } + + if (attempt >= MAX_ATTEMPTS) { + // Too many failed attempts, give up + throw err; + } + } + } + } + private async _resolveAuthorityAgain(): Promise { const remoteAuthority = this._environmentService.remoteAuthority; if (!remoteAuthority) { @@ -473,14 +499,9 @@ export abstract class ElectronExtensionService extends AbstractExtensionService } let resolverResult: ResolverResult; - - const sw = StopWatch.create(false); - this._logService.info(`Invoking resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)})`); try { - resolverResult = await this._resolveAuthority(remoteAuthority); - this._logService.info(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${resolverResult.authority.host}:${resolverResult.authority.port}' after ${sw.elapsed()} ms`); + resolverResult = await this._resolveAuthorityInitial(remoteAuthority); } catch (err) { - this._logService.error(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed()} ms`, err); if (RemoteAuthorityResolverError.isNoResolverFound(err)) { err.isHandled = await this._handleNoResolverFound(remoteAuthority); } else { diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 25fca6a0136..0aebd31d5b9 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -268,7 +268,7 @@ export class BrowserHostService extends Disposable implements IHostService { input2: { resource: editors[1].resource }, base: { resource: editors[2].resource }, result: { resource: editors[3].resource }, - options: { pinned: true } + options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready }); } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index fe4650f771f..f75556ad8a3 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -743,7 +743,9 @@ class UserKeybindings extends Disposable { private async whenCurrentProfieChanged(e: DidChangeUserDataProfileEvent): Promise { if (e.preserveData) { - await this.fileService.copy(e.previous.keybindingsResource, e.profile.keybindingsResource); + if (await this.fileService.exists(e.previous.keybindingsResource)) { + await this.fileService.copy(e.previous.keybindingsResource, e.profile.keybindingsResource); + } } this.watch(); this.reloadConfigurationScheduler.schedule(); diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index b680533dabc..4135a251e94 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -402,33 +402,32 @@ export class LabelService extends Disposable implements ILabelService { } private formatUri(resource: URI, formatting: ResourceLabelFormatting, forceNoTildify?: boolean): string { - let label = typeof formatting.label !== 'string' ? formatting.label(resource) : - formatting.label.replace(labelMatchingRegexp, (match, token, qsToken, qsValue) => { - switch (token) { - case 'scheme': return resource.scheme; - case 'authority': return resource.authority; - case 'authoritySuffix': { - const i = resource.authority.indexOf('+'); - return i === -1 ? resource.authority : resource.authority.slice(i + 1); - } - case 'path': - return formatting.stripPathStartingSeparator - ? resource.path.slice(resource.path[0] === formatting.separator ? 1 : 0) - : resource.path; - default: { - if (qsToken === 'query') { - const { query } = resource; - if (query && query[0] === '{' && query[query.length - 1] === '}') { - try { - return JSON.parse(query)[qsValue] || ''; - } catch { } - } - } - - return ''; - } + let label = formatting.label.replace(labelMatchingRegexp, (match, token, qsToken, qsValue) => { + switch (token) { + case 'scheme': return resource.scheme; + case 'authority': return resource.authority; + case 'authoritySuffix': { + const i = resource.authority.indexOf('+'); + return i === -1 ? resource.authority : resource.authority.slice(i + 1); } - }); + case 'path': + return formatting.stripPathStartingSeparator + ? resource.path.slice(resource.path[0] === formatting.separator ? 1 : 0) + : resource.path; + default: { + if (qsToken === 'query') { + const { query } = resource; + if (query && query[0] === '{' && query[query.length - 1] === '}') { + try { + return JSON.parse(query)[qsValue] || ''; + } catch { } + } + } + + return ''; + } + } + }); // convert \c:\something => C:\something if (formatting.normalizeDriveLetter && hasDriveLetterIgnorePlatform(label)) { diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index 338660d225b..745055c930e 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -164,18 +164,6 @@ suite('URI Label', () => { assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END'); }); - test('custom formatting function', function () { - labelService.registerFormatter({ - scheme: 'vscode', - formatting: { - label: (resource) => { return resource.toString(); }, - separator: '/', - } - }); - - const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); - assert.strictEqual(labelService.getUriLabel(uri1), uri1.toString()); - }); test('label caching', () => { const m = new Memento('cachedResourceLabelFormatters', storageService).getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 3ad179570b6..f181154fe05 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -15,7 +15,7 @@ import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/pla import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { ITelemetryServiceConfig, TelemetryService as BaseTelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { ITelemetryAppender, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { isInternalTelemetry, ITelemetryAppender, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/browser/workbenchCommonProperties'; @@ -40,12 +40,13 @@ export class TelemetryService extends Disposable implements ITelemetryService { if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.ariaKey) { // If remote server is present send telemetry through that, else use the client side appender const appenders = []; - const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender(configurationService, 'monacoworkbench', null, productService.aiConfig?.ariaKey); + const isInternal = isInternalTelemetry(productService, configurationService); + const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender(isInternal, 'monacoworkbench', null, productService.aiConfig?.ariaKey); appenders.push(telemetryProvider); appenders.push(new TelemetryLogAppender(loggerService, environmentService)); const config: ITelemetryServiceConfig = { appenders, - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.remoteAuthority, productService.embedderIdentifier, productService.removeTelemetryMachineId, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, isInternal, environmentService.remoteAuthority, productService.embedderIdentifier, productService.removeTelemetryMachineId, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), sendErrorTelemetry: this.sendErrorTelemetry, }; diff --git a/src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts b/src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts index 16bce794a23..81c10410b03 100644 --- a/src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts @@ -24,12 +24,13 @@ export async function resolveWorkbenchCommonProperties( storageService: IStorageService, commit: string | undefined, version: string | undefined, + isInternalTelemetry: boolean, remoteAuthority?: string, productIdentifier?: string, removeMachineId?: boolean, resolveAdditionalProperties?: () => { [key: string]: any } -): Promise<{ [name: string]: string | undefined }> { - const result: { [name: string]: string | undefined } = Object.create(null); +): Promise<{ [name: string]: string | boolean | undefined }> { + const result: { [name: string]: string | boolean | undefined } = Object.create(null); const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.APPLICATION)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.APPLICATION)!; @@ -75,6 +76,11 @@ export async function resolveWorkbenchCommonProperties( // __GDPR__COMMON__ "common.isTouchDevice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['common.isTouchDevice'] = String(Gesture.isTouchDevice()); + if (isInternalTelemetry) { + // __GDPR__COMMON__ "common.msftInternal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + result['common.msftInternal'] = isInternalTelemetry; + } + // dynamic properties which value differs on each call let seq = 0; const startTime = Date.now(); diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts index a91c4a23be9..0645973a7bb 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITelemetryService, ITelemetryInfo, ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils'; +import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable } from 'vs/base/common/lifecycle'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; @@ -37,10 +37,11 @@ export class TelemetryService extends Disposable implements ITelemetryService { super(); if (supportsTelemetry(productService, environmentService)) { + const isInternal = isInternalTelemetry(productService, configurationService); const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appenders: [new TelemetryAppenderClient(channel)], - commonProperties: resolveWorkbenchCommonProperties(storageService, fileService, environmentService.os.release, environmentService.os.hostname, productService.commit, productService.version, environmentService.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, fileService, environmentService.os.release, environmentService.os.hostname, productService.commit, productService.version, environmentService.machineId, isInternal, environmentService.installSourcePath, environmentService.remoteAuthority), piiPaths: getPiiPathsFromEnvironment(environmentService), sendErrorTelemetry: true }; diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts b/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts index 9d5b4282308..83f9b2c2a7b 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts +++ b/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts @@ -18,11 +18,11 @@ export async function resolveWorkbenchCommonProperties( commit: string | undefined, version: string | undefined, machineId: string, - msftInternalDomains: string[] | undefined, + isInternalTelemetry: boolean, installSourcePath: string, remoteAuthority?: string ): Promise<{ [name: string]: string | boolean | undefined }> { - const result = await resolveCommonProperties(fileService, release, hostname, process.arch, commit, version, machineId, msftInternalDomains, installSourcePath); + const result = await resolveCommonProperties(fileService, release, hostname, process.arch, commit, version, machineId, isInternalTelemetry, installSourcePath); const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.APPLICATION)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.APPLICATION)!; diff --git a/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts b/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts index c3f2b77b696..42640f42bc7 100644 --- a/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts +++ b/src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts @@ -23,7 +23,7 @@ suite('Browser Telemetry - common properties', function () { }; }; - const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, undefined, false, resolveCommonTelemetryProperties); + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, false, undefined, undefined, false, resolveCommonTelemetryProperties); assert.ok('commitHash' in props); assert.ok('sessionID' in props); @@ -53,10 +53,10 @@ suite('Browser Telemetry - common properties', function () { }); }; - const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, undefined, false, resolveCommonTelemetryProperties); + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, false, undefined, undefined, false, resolveCommonTelemetryProperties); assert.strictEqual(props['userId'], 1); - const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, undefined, false, resolveCommonTelemetryProperties); + const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, false, undefined, undefined, false, resolveCommonTelemetryProperties); assert.strictEqual(props2['userId'], 2); }); }); diff --git a/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts b/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts index 8ae51addd3e..a4813d2485a 100644 --- a/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts @@ -46,7 +46,7 @@ suite('Telemetry - common properties', function () { test('default', async function () { await Promises.mkdir(parentDir, { recursive: true }); fs.writeFileSync(installSource, 'my.install.source'); - const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); + const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', false, installSource); assert.ok('commitHash' in props); assert.ok('sessionID' in props); assert.ok('timestamp' in props); @@ -66,7 +66,7 @@ suite('Telemetry - common properties', function () { // machine id et al assert.ok('common.machineId' in props, 'machineId'); fs.unlinkSync(installSource); - const props_1 = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); + const props_1 = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', false, installSource); assert.ok(!('common.source' in props_1)); }); @@ -74,14 +74,14 @@ suite('Telemetry - common properties', function () { testStorageService.store('telemetry.lastSessionDate', new Date().toUTCString(), StorageScope.APPLICATION, StorageTarget.MACHINE); - const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); + const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', false, installSource); assert.ok('common.lastSessionDate' in props); // conditional, see below assert.ok('common.isNewSession' in props); assert.strictEqual(props['common.isNewSession'], '0'); }); test('values chance on ask', async function () { - const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); + const props = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', false, installSource); let value1 = props['common.sequence']; let value2 = props['common.sequence']; assert.ok(value1 !== value2, 'seq'); diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 5fab982a11c..7b124b207ca 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -87,7 +87,16 @@ export class UserDataProfileManagementService extends Disposable implements IUse } private async enterProfile(profile: IUserDataProfile, preserveData: boolean, reloadMessage?: string): Promise { - if (this.environmentService.remoteAuthority) { + const isRemoteWindow = !!this.environmentService.remoteAuthority; + + if (!isRemoteWindow) { + this.extensionService.stopExtensionHosts(); + } + + // In a remote window update current profile before reloading so that data is preserved from current profile if asked to preserve + await this.userDataProfileService.updateCurrentProfile(profile, preserveData); + + if (isRemoteWindow) { const result = await this.dialogService.confirm({ type: 'info', message: reloadMessage ?? localize('reload message', "Switching a settings profile requires reloading VS Code."), @@ -96,12 +105,9 @@ export class UserDataProfileManagementService extends Disposable implements IUse if (result.confirmed) { await this.hostService.reload(); } - return; + } else { + await this.extensionService.startExtensionHosts(); } - - this.extensionService.stopExtensionHosts(); - await this.userDataProfileService.updateCurrentProfile(profile, preserveData); - await this.extensionService.startExtensionHosts(); } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 4800d7148ee..b0c26eacd7f 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -347,4 +347,7 @@ import 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; // Deprecated Extension Migrator import 'vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution'; +// Bracket Pair Colorizer 2 Telemetry +import 'vs/workbench/contrib/bracketPairColorizer2Telemetry/browser/bracketPairColorizer2Telemetry.contribution'; + //#endregion diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index a71d4de98e3..3176a04cdb2 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -176,7 +176,7 @@ import 'vs/workbench/contrib/offline/browser/offline.contribution'; // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -import { create, commands, env, window, logger } from 'vs/workbench/browser/web.factory'; +import { create, commands, env, window, workspace, logger } from 'vs/workbench/browser/web.factory'; import { IWorkbench, ICommand, ICommonTelemetryPropertiesResolver, IDefaultEditor, IDefaultLayout, IDefaultView, IDevelopmentOptions, IExternalUriResolver, IExternalURLOpener, IHomeIndicator, IInitialColorTheme, IPosition, IProductQualityChangeHandler, IRange, IResourceUriProvider, ISettingsSyncOptions, IShowPortCandidate, ITunnel, ITunnelFactory, ITunnelOptions, ITunnelProvider, IWelcomeBanner, IWelcomeBannerAction, IWindowIndicator, IWorkbenchConstructionOptions, Menu } from 'vs/workbench/browser/web.api'; import { UriComponents, URI } from 'vs/base/common/uri'; import { IWebSocketFactory, IWebSocket } from 'vs/platform/remote/browser/browserSocketFactory'; @@ -281,6 +281,9 @@ export { // Env env, + // Workspace + workspace, + // Development IDevelopmentOptions }; diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 8ddc0b0377f..36aee9c2f76 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -5430,7 +5430,9 @@ declare module 'vscode' { /** * Provider which handles dropping of resources into a text editor. * - * The user can drop into a text editor by holding down `shift` while dragging. Requires `workbench.editor.dropIntoEditor.enabled` to be on. + * This allows users to drag and drop resources (including resources from external apps) into the editor. While dragging + * and dropping files, users can hold down `shift` to drop the file into the editor instead of opening it. + * Requires `editor.dropIntoEditor.enabled` to be on. */ export interface DocumentDropEditProvider { /** @@ -10184,7 +10186,7 @@ declare module 'vscode' { * @param callbackfn Callback for iteration through the data transfer items. * @param thisArg The `this` context used when invoking the handler function. */ - forEach(callbackfn: (value: DataTransferItem, key: string, dataTransfer: DataTransfer) => void, thisArg?: any): void; + forEach(callbackfn: (item: DataTransferItem, mimeType: string, dataTransfer: DataTransfer) => void, thisArg?: any): void; /** * Get a new iterator with the `[mime, item]` pairs for each element in this data transfer. @@ -15061,7 +15063,7 @@ declare module 'vscode' { /** * Optional commenting range provider. Provide a list {@link Range ranges} which support commenting to any given resource uri. * - * If not provided, users can leave comments in any document opened in the editor. + * If not provided, users cannot leave any comments. */ commentingRangeProvider?: CommentingRangeProvider;