diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3ec839df4ef52..f121b855a0d42 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -140,6 +140,7 @@ function f(x: number, y: string): void { } - Do not duplicate code. Always look for existing utility functions, helpers, or patterns in the codebase before implementing new functionality. Reuse and extend existing code whenever possible. - You MUST deal with disposables by registering them immediately after creation for later disposal. Use helpers such as `DisposableStore`, `MutableDisposable` or `DisposableMap`. Do NOT register a disposable to the containing class if the object is created within a method that is called repeadedly to avoid leaks. Instead, return a `IDisposable` from such method and let the caller register it. - You MUST NOT use storage keys of another component only to make changes to that component. You MUST come up with proper API to change another component. +- Use `IEditorService` to open editors instead of `IEditorGroupsService.activeGroup.openEditor` to ensure that the editor opening logic is properly followed and to avoid bypassing important features such as `revealIfOpened` or `preserveFocus`. ## Learnings - Minimize the amount of assertions in tests. Prefer one snapshot-style `assert.deepStrictEqual` over multiple precise assertions, as they are much more difficult to understand and to update. diff --git a/.github/skills/agent-sessions-layout/SKILL.md b/.github/skills/agent-sessions-layout/SKILL.md index f818e2dc75e08..a76794d9c7d8a 100644 --- a/.github/skills/agent-sessions-layout/SKILL.md +++ b/.github/skills/agent-sessions-layout/SKILL.md @@ -61,7 +61,6 @@ When proposing or implementing changes, follow these rules from the spec: | `sessions/browser/style.css` | Layout-specific styles | | `sessions/browser/parts/` | Agent session part implementations | | `sessions/browser/parts/titlebarPart.ts` | Titlebar part, MainTitlebarPart, AuxiliaryTitlebarPart, TitleService | -| `sessions/browser/parts/editorModal.ts` | Editor modal overlay | | `sessions/browser/parts/sidebarPart.ts` | Sidebar part (with footer) | | `sessions/browser/parts/chatBarPart.ts` | Chat Bar part | | `sessions/browser/widget/` | Agent sessions chat widget | @@ -76,5 +75,5 @@ After modifying layout code: 1. Verify the build compiles without errors via the `VS Code - Build` task 2. Ensure the grid structure matches the spec's visual representation 3. Confirm part visibility toggling works correctly (show/hide/maximize) -4. Test the editor modal opens/closes properly on editor events +4. Test that editors open in the `ModalEditorPart` overlay and that it closes properly 5. Verify sidebar footer renders with account widget diff --git a/.github/skills/sessions/SKILL.md b/.github/skills/sessions/SKILL.md index 010c3779a7253..4889632066bf7 100644 --- a/.github/skills/sessions/SKILL.md +++ b/.github/skills/sessions/SKILL.md @@ -84,7 +84,6 @@ src/vs/sessions/ │ ├── auxiliaryBarPart.ts # Auxiliary Bar (with run script dropdown) │ ├── panelPart.ts # Panel (terminal, output, etc.) │ ├── projectBarPart.ts # Project bar (folder entries) -│ ├── editorModal.ts # Editor modal overlay │ ├── agentSessionsChatInputPart.ts # Chat input part adapter │ ├── agentSessionsChatWelcomePart.ts # Welcome view (mascot + target buttons + pickers) │ └── media/ # Part CSS files @@ -134,13 +133,13 @@ Use the `agent-sessions-layout` skill for detailed guidance on the layout. Key p | Chat Bar | Visible | Primary chat widget | | Auxiliary Bar | Visible | Changes view, etc. | | Panel | Hidden | Terminal, output | -| Editor | Hidden | Modal overlay, auto-shows on editor open | +| Editor | Hidden | Main part hidden; editors open via `MODAL_GROUP` into `ModalEditorPart` | **Not included:** Activity Bar, Status Bar, Banner. ### 4.3 Editor Modal -Editors appear as modal overlays (80% of workbench, min 400×300, max 1200×900). The modal auto-shows when an editor opens and auto-hides when all editors close. Click backdrop, press Escape, or click X to dismiss. +The main editor part is hidden (`display:none`). All editors open via `MODAL_GROUP` into the standard `ModalEditorPart` overlay (created on-demand by `EditorParts.createModalEditorPart`). The sessions configuration sets `workbench.editor.useModal` to `'on'`, which causes `findGroup()` to redirect all editor opens to the modal. Click backdrop or press Escape to dismiss. ## 5. Chat Widget diff --git a/.github/skills/update-screenshots/SKILL.md b/.github/skills/update-screenshots/SKILL.md new file mode 100644 index 0000000000000..f4cac151d61d8 --- /dev/null +++ b/.github/skills/update-screenshots/SKILL.md @@ -0,0 +1,87 @@ +--- +name: update-screenshots +description: Download screenshot baselines from the latest CI run and commit them. Use when asked to update, accept, or refresh component screenshot baselines from CI, or after the screenshot-test GitHub Action reports differences. This skill should be run as a subagent. +--- + +# Update Component Screenshots from CI + +When asked to update, accept, or refresh screenshot baselines from CI — or when the `Screenshot Tests` GitHub Action has failed with screenshot differences — follow this procedure to download the CI-generated screenshots and commit them as the new baselines. + +## Why CI Screenshots? + +Screenshots captured locally may differ from CI due to platform differences (fonts, rendering, DPI). The CI (Linux, ubuntu-latest) is the source of truth. This skill downloads the CI-produced screenshots and commits them as baselines. + +## Prerequisites + +- The `gh` CLI must be authenticated (`gh auth status`). +- The `Screenshot Tests` GitHub Action must have run and produced a `screenshot-diff` artifact. + +## Procedure + +### 1. Find the latest screenshot artifact + +If the user provides a specific run ID or PR number, use that. Otherwise, find the latest run: + +```bash +# For a specific PR: +gh run list --workflow screenshot-test.yml --branch --limit 5 --json databaseId,status,conclusion,headBranch + +# For the current branch: +gh run list --workflow screenshot-test.yml --branch $(git branch --show-current) --limit 5 --json databaseId,status,conclusion +``` + +Pick the most recent run that has a `screenshot-diff` artifact (runs where screenshots matched won't have one). + +### 2. Download the artifact + +```bash +gh run download --name screenshot-diff --dir .tmp/screenshot-diff +``` + +This downloads: +- `test/componentFixtures/.screenshots/current/` — the CI-captured screenshots +- `test/componentFixtures/.screenshots/report.json` — structured diff report +- `test/componentFixtures/.screenshots/report.md` — human-readable diff report + +### 3. Review the changes + +Show the user what changed by reading the markdown report: + +```bash +cat .tmp/screenshot-diff/test/componentFixtures/.screenshots/report.md +``` + +### 4. Copy CI screenshots to baseline + +```bash +# Remove old baselines and replace with CI screenshots +rm -rf test/componentFixtures/.screenshots/baseline/ +cp -r .tmp/screenshot-diff/test/componentFixtures/.screenshots/current/ test/componentFixtures/.screenshots/baseline/ +``` + +### 5. Clean up + +```bash +rm -rf .tmp/screenshot-diff +``` + +### 6. Stage and commit + +```bash +git add test/componentFixtures/.screenshots/baseline/ +git commit -m "update screenshot baselines from CI" +``` + +### 7. Verify + +Confirm the baselines are updated by listing the files: + +```bash +git diff --stat HEAD~1 +``` + +## Notes + +- If no `screenshot-diff` artifact exists, the screenshots already match the baselines — no update needed. +- The `--filter` option on the CLI can be used to selectively accept only some fixtures if needed. +- After committing updated baselines, the next CI run should pass the screenshot comparison. diff --git a/.github/workflows/screenshot-test.yml b/.github/workflows/screenshot-test.yml new file mode 100644 index 0000000000000..e5a91228a1419 --- /dev/null +++ b/.github/workflows/screenshot-test.yml @@ -0,0 +1,125 @@ +name: Screenshot Tests + +on: + push: + branches: [main] + pull_request: + branches: + - main + - 'release/*' + +permissions: + contents: read + pull-requests: write + checks: write + +concurrency: + group: screenshots-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + screenshots: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + + - name: Install dependencies + run: npm ci --ignore-scripts + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install build/vite dependencies + run: rm -f package-lock.json && npm install + working-directory: build/vite + + - name: Install Playwright Chromium + run: npx playwright install chromium + + - name: Capture screenshots + run: npx component-explorer screenshot --project ./test/componentFixtures/component-explorer.json + + - name: Compare screenshots + id: compare + run: | + npx component-explorer screenshot:compare \ + --project ./test/componentFixtures \ + --report ./test/componentFixtures/.screenshots/report.json \ + --report-markdown ./test/componentFixtures/.screenshots/report.md + continue-on-error: true + + - name: Upload screenshot report + if: steps.compare.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + name: screenshot-diff + path: | + test/componentFixtures/.screenshots/current/ + test/componentFixtures/.screenshots/report.json + test/componentFixtures/.screenshots/report.md + + - name: Set check title + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + REPORT="test/componentFixtures/.screenshots/report.json" + if [ -f "$REPORT" ]; then + CHANGED=$(node -e "const r = require('./$REPORT'); console.log(r.summary.added + r.summary.removed + r.summary.changed)") + TITLE="${CHANGED} screenshots changed" + else + TITLE="Screenshots match" + fi + + SHA="${{ github.event.pull_request.head.sha || github.sha }}" + CHECK_RUN_ID=$(gh api "repos/${{ github.repository }}/commits/$SHA/check-runs" \ + --jq '.check_runs[] | select(.name == "screenshots") | .id') + + if [ -n "$CHECK_RUN_ID" ]; then + gh api "repos/${{ github.repository }}/check-runs/$CHECK_RUN_ID" \ + -X PATCH --input - <> $GITHUB_STEP_SUMMARY + else + echo "## Screenshots ✅" >> $GITHUB_STEP_SUMMARY + echo "No visual changes detected." >> $GITHUB_STEP_SUMMARY + fi + + # - name: Post PR comment + # if: github.event_name == 'pull_request' + # env: + # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # run: | + # COMMENT_MARKER="" + # BODY="$COMMENT_MARKER"$'\n' + # + # if [ -f test/componentFixtures/.screenshots/report.md ]; then + # BODY+=$(cat test/componentFixtures/.screenshots/report.md) + # BODY+=$'\n\n' + # BODY+="📦 [Download the \`screenshot-diff\` artifact](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) to review images." + # else + # BODY+="## Screenshots ✅"$'\n\n' + # BODY+="No visual changes detected." + # fi + # + # # Find existing comment + # EXISTING=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ + # --paginate --jq ".[] | select(.body | startswith(\"$COMMENT_MARKER\")) | .id" | head -1) + # + # if [ -n "$EXISTING" ]; then + # gh api "repos/${{ github.repository }}/issues/comments/$EXISTING" -X PATCH -f body="$BODY" + # else + # gh pr comment "${{ github.event.pull_request.number }}" --body "$BODY" + # fi diff --git a/.gitignore b/.gitignore index 814f7eb1787e7..7359fbeaa220c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ product.overrides.json .vscode-test vscode-telemetry-docs/ test-output.json +test/componentFixtures/.screenshots/* +!test/componentFixtures/.screenshots/baseline/ diff --git a/build/vite/package-lock.json b/build/vite/package-lock.json index 4db5338149d1f..476a7ae626f58 100644 --- a/build/vite/package-lock.json +++ b/build/vite/package-lock.json @@ -8,655 +8,56 @@ "name": "@vscode/sample-source", "version": "0.0.0", "devDependencies": { - "@vscode/component-explorer": "next", - "@vscode/component-explorer-vite-plugin": "next", + "@vscode/component-explorer": "^0.1.1-10", + "@vscode/component-explorer-vite-plugin": "^0.1.1-10", "@vscode/rollup-plugin-esm-url": "^1.0.1-1", + "rollup": "*", "vite": "npm:rolldown-vite@latest" } }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@oxc-project/runtime": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.101.0.tgz", - "integrity": "sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.101.0.tgz", - "integrity": "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.53.tgz", - "integrity": "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.53.tgz", - "integrity": "sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.53.tgz", - "integrity": "sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.53.tgz", - "integrity": "sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.53.tgz", - "integrity": "sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.53.tgz", - "integrity": "sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.53.tgz", - "integrity": "sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.53.tgz", - "integrity": "sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.53.tgz", - "integrity": "sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.53.tgz", - "integrity": "sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.53.tgz", - "integrity": "sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.53.tgz", - "integrity": "sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.53.tgz", - "integrity": "sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", - "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], + "node_modules/@oxc-project/runtime": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.101.0.tgz", + "integrity": "sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], + "node_modules/@oxc-project/types": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.101.0.tgz", + "integrity": "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true + "funding": { + "url": "https://github.com/sponsors/Boshen" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.53.tgz", + "integrity": "sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "openbsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, "os": [ "win32" ], - "peer": true + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true + "license": "MIT" }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.57.1", @@ -670,8 +71,7 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.57.1", @@ -685,42 +85,29 @@ "optional": true, "os": [ "win32" - ], - "peer": true - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + ] }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@vscode/component-explorer": { - "version": "0.1.1-2", - "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-2.tgz", - "integrity": "sha512-2VMoXLnDBk+hKrhw+iGUsEjnCd1YiiZqe+1LdQIKdk16zqYRtJ5iO6yDxZ4cKy3Wphd+qLDUWmZSULNtKioMrQ==", + "version": "0.1.1-10", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-10.tgz", + "integrity": "sha512-Nokjk2DB1hgKeUL1FW5dHfXySgj17BgxcsiyzcG6etdFIbMpzv85nMQxrW/88aklgmJPrRVefMRHFYSds/F3/g==", "dev": true, - "peerDependencies": { + "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@vscode/component-explorer-vite-plugin": { - "version": "0.1.1-2", - "resolved": "https://registry.npmjs.org/@vscode/component-explorer-vite-plugin/-/component-explorer-vite-plugin-0.1.1-2.tgz", - "integrity": "sha512-iYSp8shDZEJJrjMWGneWyjFbFyED5Og74c9h5XBmVPZBDN4INfOTmPlC+HYTv/CL5+NFxpl91CdtacCmqz2EXw==", + "version": "0.1.1-10", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer-vite-plugin/-/component-explorer-vite-plugin-0.1.1-10.tgz", + "integrity": "sha512-1F2Ier7lpFPvYzWxyNCBy3qYzSwRyTw6k3pm+l6DBMMNT+OTnCZ3+awa7wtijZXMc4O1WooxswjrjBu++Oqftg==", "dev": true, "dependencies": { "tinyglobby": "^0.2.0" @@ -768,28 +155,12 @@ } } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lightningcss": { "version": "1.31.1", @@ -821,216 +192,6 @@ "lightningcss-win32-x64-msvc": "1.31.1" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", - "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", - "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", - "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", - "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", - "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", - "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", - "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", - "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", - "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", - "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", @@ -1058,7 +219,6 @@ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -1140,7 +300,6 @@ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -1154,7 +313,6 @@ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -1201,7 +359,6 @@ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -1247,7 +404,6 @@ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -1279,14 +435,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/vite": { "name": "rolldown-vite", "version": "7.3.1", diff --git a/build/vite/package.json b/build/vite/package.json index 9800e54af40f1..b4268fceca1c3 100644 --- a/build/vite/package.json +++ b/build/vite/package.json @@ -9,14 +9,10 @@ "preview": "vite preview" }, "devDependencies": { + "@vscode/component-explorer": "^0.1.1-10", + "@vscode/component-explorer-vite-plugin": "^0.1.1-10", "@vscode/rollup-plugin-esm-url": "^1.0.1-1", - "vite": "npm:rolldown-vite@latest", - "@vscode/component-explorer": "next", - "@vscode/component-explorer-vite-plugin": "next" - }, - "overrides": { - "@vscode/component-explorer-vite-plugin": { - "vite": "$vite" - } + "rollup": "*", + "vite": "npm:rolldown-vite@latest" } } diff --git a/build/vite/vite.config.ts b/build/vite/vite.config.ts index 6cdde88076a49..d6d8931fa315d 100644 --- a/build/vite/vite.config.ts +++ b/build/vite/vite.config.ts @@ -170,13 +170,13 @@ export default defineConfig({ createHotClassSupport(), componentExplorer({ logLevel: 'verbose', - include: 'build/vite/**/*.fixture.ts', + include: join(__dirname, '../../src/**/*.fixture.ts'), }), ], customLogger: logger, resolve: { alias: { - '~@vscode/codicons': '/node_modules/@vscode/codicons', + '~@vscode/codicons': join(__dirname, '../../node_modules/@vscode/codicons'), } }, esbuild: { @@ -198,7 +198,6 @@ export default defineConfig({ server: { cors: true, port: 5199, - origin: 'http://localhost:5199', fs: { allow: [ // To allow loading from sources, not needed when loading monaco-editor from npm package diff --git a/extensions/debug-auto-launch/.vscodeignore b/extensions/debug-auto-launch/.vscodeignore index 1ebf0088b62a4..0628555db0021 100644 --- a/extensions/debug-auto-launch/.vscodeignore +++ b/extensions/debug-auto-launch/.vscodeignore @@ -1,5 +1,5 @@ src/** tsconfig*.json out/** -extension.webpack.config.js +esbuild*.mts package-lock.json diff --git a/extensions/git-base/extension-browser.webpack.config.js b/extensions/debug-auto-launch/esbuild.mts similarity index 51% rename from extensions/git-base/extension-browser.webpack.config.js rename to extensions/debug-auto-launch/esbuild.mts index fcdf954744c12..2b75ca703da06 100644 --- a/extensions/git-base/extension-browser.webpack.config.js +++ b/extensions/debug-auto-launch/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts' +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), }, - output: { - filename: 'extension.js' - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/debug-server-ready/.vscodeignore b/extensions/debug-server-ready/.vscodeignore index 04b88eb4a4520..7b79c7b47b449 100644 --- a/extensions/debug-server-ready/.vscodeignore +++ b/extensions/debug-server-ready/.vscodeignore @@ -1,6 +1,6 @@ src/** tsconfig*.json out/** -extension.webpack.config.js +esbuild*.mts package-lock.json .vscode diff --git a/extensions/git-base/extension.webpack.config.js b/extensions/debug-server-ready/esbuild.mts similarity index 51% rename from extensions/git-base/extension.webpack.config.js rename to extensions/debug-server-ready/esbuild.mts index 0bea7c7e821e4..2b75ca703da06 100644 --- a/extensions/git-base/extension.webpack.config.js +++ b/extensions/debug-server-ready/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts' +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), }, - output: { - filename: 'extension.js' - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/git-base/.vscodeignore b/extensions/git-base/.vscodeignore index 835918d940378..44bb9f7ee78e3 100644 --- a/extensions/git-base/.vscodeignore +++ b/extensions/git-base/.vscodeignore @@ -1,7 +1,6 @@ src/** build/** cgmanifest.json -extension.webpack.config.js -extension-browser.webpack.config.js +esbuild*.mts tsconfig*.json diff --git a/extensions/git-base/esbuild.browser.mts b/extensions/git-base/esbuild.browser.mts new file mode 100644 index 0000000000000..bf525ffa1efcb --- /dev/null +++ b/extensions/git-base/esbuild.browser.mts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +run({ + platform: 'browser', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/debug-auto-launch/extension.webpack.config.js b/extensions/git-base/esbuild.mts similarity index 51% rename from extensions/debug-auto-launch/extension.webpack.config.js rename to extensions/git-base/esbuild.mts index 0c857b362f5da..2b75ca703da06 100644 --- a/extensions/debug-auto-launch/extension.webpack.config.js +++ b/extensions/git-base/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts', +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), }, - resolve: { - mainFields: ['module', 'main'] - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/git-base/tsconfig.browser.json b/extensions/git-base/tsconfig.browser.json new file mode 100644 index 0000000000000..dbacbb22fdff4 --- /dev/null +++ b/extensions/git-base/tsconfig.browser.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ] +} diff --git a/extensions/ipynb/.vscodeignore b/extensions/ipynb/.vscodeignore index 8805685e2220b..6823ef2cb7ab9 100644 --- a/extensions/ipynb/.vscodeignore +++ b/extensions/ipynb/.vscodeignore @@ -3,8 +3,6 @@ src/** notebook-src/** out/** tsconfig*.json -extension.webpack.config.js -extension-browser.webpack.config.js +esbuild*.mts package-lock.json .gitignore -esbuild.* diff --git a/extensions/ipynb/esbuild.browser.mts b/extensions/ipynb/esbuild.browser.mts new file mode 100644 index 0000000000000..6b08f26043b37 --- /dev/null +++ b/extensions/ipynb/esbuild.browser.mts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +run({ + platform: 'browser', + entryPoints: { + 'ipynbMain.browser': path.join(srcDir, 'ipynbMain.browser.ts'), + 'notebookSerializerWorker': path.join(srcDir, 'notebookSerializerWorker.web.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/ipynb/esbuild.mts b/extensions/ipynb/esbuild.mts new file mode 100644 index 0000000000000..3aebefcf4dfb7 --- /dev/null +++ b/extensions/ipynb/esbuild.mts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'ipynbMain.node': path.join(srcDir, 'ipynbMain.node.ts'), + 'notebookSerializerWorker': path.join(srcDir, 'notebookSerializerWorker.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/ipynb/extension-browser.webpack.config.js b/extensions/ipynb/extension-browser.webpack.config.js deleted file mode 100644 index 08a0edc8ad03e..0000000000000 --- a/extensions/ipynb/extension-browser.webpack.config.js +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -import path from 'path'; - -const mainConfig = withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/ipynbMain.browser.ts' - }, - output: { - filename: 'ipynbMain.browser.js', - path: path.join(import.meta.dirname, 'dist', 'browser') - } -}); - - -const workerConfig = withBrowserDefaults({ - context: import.meta.dirname, - entry: { - notebookSerializerWorker: './src/notebookSerializerWorker.web.ts', - }, - output: { - filename: 'notebookSerializerWorker.js', - path: path.join(import.meta.dirname, 'dist', 'browser'), - libraryTarget: 'var', - library: 'serverExportVar' - }, -}); - -export default [mainConfig, workerConfig]; diff --git a/extensions/ipynb/extension.webpack.config.js b/extensions/ipynb/extension.webpack.config.js deleted file mode 100644 index 2bfea01dd914f..0000000000000 --- a/extensions/ipynb/extension.webpack.config.js +++ /dev/null @@ -1,22 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults, { nodePlugins } from '../shared.webpack.config.mjs'; -import path from 'path'; - -export default withDefaults({ - context: import.meta.dirname, - entry: { - ['ipynbMain.node']: './src/ipynbMain.node.ts', - notebookSerializerWorker: './src/notebookSerializerWorker.ts', - }, - output: { - path: path.resolve(import.meta.dirname, 'dist'), - filename: '[name].js' - }, - plugins: [ - ...nodePlugins(import.meta.dirname), // add plugins, don't replace inherited - ] -}); diff --git a/extensions/ipynb/tsconfig.browser.json b/extensions/ipynb/tsconfig.browser.json new file mode 100644 index 0000000000000..27cbc984bb210 --- /dev/null +++ b/extensions/ipynb/tsconfig.browser.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ], + "files": [ + "./src/ipynbMain.browser.ts", + "./src/notebookSerializerWorker.web.ts" + ] +} diff --git a/extensions/media-preview/package.json b/extensions/media-preview/package.json index f1ee36118f2eb..838a94e6396a1 100644 --- a/extensions/media-preview/package.json +++ b/extensions/media-preview/package.json @@ -156,8 +156,12 @@ "watch": "npm run build-preview && gulp watch-extension:media-preview", "vscode:prepublish": "npm run build-ext", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:media-preview ./tsconfig.json", - "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", - "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + "compile-web": "npm-run-all2 -lp bundle-web typecheck-web", + "bundle-web": "node ./esbuild.browser.mts", + "typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit", + "watch-web": "npm-run-all2 -lp watch-bundle-web watch-typecheck-web", + "watch-bundle-web": "node ./esbuild.browser.mts --watch", + "watch-typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit --watch" }, "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/extensions/mermaid-chat-features/package.json b/extensions/mermaid-chat-features/package.json index ba67e03fdcee3..fed905d2651ec 100644 --- a/extensions/mermaid-chat-features/package.json +++ b/extensions/mermaid-chat-features/package.json @@ -118,8 +118,12 @@ "vscode:prepublish": "npm run build-ext && npm run build-chat-webview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:mermaid-chat-features", "build-chat-webview": "node ./esbuild.webview.mts", - "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", - "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + "compile-web": "npm-run-all2 -lp bundle-web typecheck-web", + "bundle-web": "node ./esbuild.browser.mts", + "typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit", + "watch-web": "npm-run-all2 -lp watch-bundle-web watch-typecheck-web", + "watch-bundle-web": "node ./esbuild.browser.mts --watch", + "watch-typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit --watch" }, "devDependencies": { "@types/node": "^22.18.10", diff --git a/extensions/microsoft-authentication/.vscodeignore b/extensions/microsoft-authentication/.vscodeignore index 3e683c7e56dd4..e61623be63b1f 100644 --- a/extensions/microsoft-authentication/.vscodeignore +++ b/extensions/microsoft-authentication/.vscodeignore @@ -2,7 +2,7 @@ .vscode-test/** out/test/** out/** -extension.webpack.config.js +esbuild*.mts package-lock.json src/** .gitignore diff --git a/extensions/microsoft-authentication/esbuild.mts b/extensions/microsoft-authentication/esbuild.mts new file mode 100644 index 0000000000000..ef8f793c26ed1 --- /dev/null +++ b/extensions/microsoft-authentication/esbuild.mts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +const isWindows = process.platform === 'win32'; +const isMacOS = process.platform === 'darwin'; +const isLinux = !isWindows && !isMacOS; + +const windowsArches = ['x64']; +const linuxArches = ['x64']; + +let platformFolder: string; +switch (process.platform) { + case 'win32': platformFolder = 'windows'; break; + case 'darwin': platformFolder = 'macos'; break; + case 'linux': platformFolder = 'linux'; break; + default: throw new Error(`Unsupported platform: ${process.platform}`); +} + +const arch = process.env.VSCODE_ARCH || process.arch; + +/** + * Copy native MSAL runtime binaries to the output directory. + */ +async function copyNativeMsalFiles(outDir: string): Promise { + if ( + !(isWindows && windowsArches.includes(arch)) && + !isMacOS && + !(isLinux && linuxArches.includes(arch)) + ) { + return; + } + + const msalRuntimeDir = path.join(import.meta.dirname, 'node_modules', '@azure', 'msal-node-runtime', 'dist', platformFolder, arch); + try { + const files = await fs.promises.readdir(msalRuntimeDir); + for (const file of files) { + if (/^(lib)?msal.*\.(node|dll|dylib|so)$/.test(file)) { + await fs.promises.copyFile(path.join(msalRuntimeDir, file), path.join(outDir, file)); + } + } + } catch { + // Skip if directory doesn't exist (unsupported platform/arch) + } +} + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), + }, + srcDir, + outdir: outDir, + additionalOptions: { + external: ['vscode', './msal-node-runtime'], + alias: { + 'keytar': path.resolve(import.meta.dirname, 'packageMocks', 'keytar', 'index.js'), + }, + }, +}, process.argv, copyNativeMsalFiles); diff --git a/extensions/microsoft-authentication/extension.webpack.config.js b/extensions/microsoft-authentication/extension.webpack.config.js deleted file mode 100644 index a46d5a527dfec..0000000000000 --- a/extensions/microsoft-authentication/extension.webpack.config.js +++ /dev/null @@ -1,69 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults, { nodePlugins } from '../shared.webpack.config.mjs'; -import CopyWebpackPlugin from 'copy-webpack-plugin'; -import path from 'path'; - -const isWindows = process.platform === 'win32'; -const isMacOS = process.platform === 'darwin'; -const isLinux = !isWindows && !isMacOS; - -const windowsArches = ['x64']; -const linuxArches = ['x64']; - -let platformFolder; -switch (process.platform) { - case 'win32': - platformFolder = 'windows'; - break; - case 'darwin': - platformFolder = 'macos'; - break; - case 'linux': - platformFolder = 'linux'; - break; - default: - throw new Error(`Unsupported platform: ${process.platform}`); -} - -const arch = process.env.VSCODE_ARCH || process.arch; -console.log(`Building Microsoft Authentication Extension for ${process.platform} (${arch})`); - -const plugins = [...nodePlugins(import.meta.dirname)]; -if ( - (isWindows && windowsArches.includes(arch)) || - isMacOS || - (isLinux && linuxArches.includes(arch)) -) { - plugins.push(new CopyWebpackPlugin({ - patterns: [ - { - // The native files we need to ship with the extension - from: `**/dist/${platformFolder}/${arch}/(lib|)msal*.(node|dll|dylib|so)`, - to: '[name][ext]' - } - ] - })); -} - -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts' - }, - externals: { - // The @azure/msal-node-runtime package requires this native node module (.node). - // It is currently only included on Windows, but the package handles unsupported platforms - // gracefully. - './msal-node-runtime': 'commonjs ./msal-node-runtime' - }, - resolve: { - alias: { - 'keytar': path.resolve(import.meta.dirname, 'packageMocks', 'keytar', 'index.js') - } - }, - plugins -}); diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index af89d698f9c12..c1bc21f212125 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -492,10 +492,10 @@ color: var(--vscode-icon-foreground) !important; } -/* Chat input toolbar icons should use proper foreground color, not the muted icon.foreground */ +/* Chat input toolbar icons should follow icon foreground token */ .monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item .codicon, .monaco-workbench .interactive-session .chat-input-toolbars .action-label .codicon { - color: var(--vscode-foreground) !important; + color: var(--vscode-icon-foreground) !important; } /* Buttons */ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index f7c55c96d9e37..50e325eac8caf 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -1977,7 +1977,11 @@ "jsonl": "_json", "postcss": "_css", "django-html": "_html_3", - "blade": "_php" + "blade": "_php", + "prompt": "_markdown", + "instructions": "_markdown", + "chatagent": "_markdown", + "skill": "_markdown" }, "light": { "file": "_default_light", @@ -2299,7 +2303,11 @@ "jsonl": "_json_light", "postcss": "_css_light", "django-html": "_html_3_light", - "blade": "_php_light" + "blade": "_php_light", + "prompt": "_markdown_light", + "instructions": "_markdown_light", + "chatagent": "_markdown_light", + "skill": "_markdown_light" }, "fileNames": { "mix": "_hex_light", @@ -2403,4 +2411,4 @@ } }, "version": "https://github.com/jesseweed/seti-ui/commit/2d6c5e68b4ded73c92dac291845ee44e1182d511" -} \ No newline at end of file +} diff --git a/extensions/vscode-test-resolver/.vscodeignore b/extensions/vscode-test-resolver/.vscodeignore index 43e7067afbd26..ec136872c1762 100644 --- a/extensions/vscode-test-resolver/.vscodeignore +++ b/extensions/vscode-test-resolver/.vscodeignore @@ -4,3 +4,4 @@ typings/** **/*.map .gitignore tsconfig*.json +esbuild*.mts diff --git a/extensions/vscode-test-resolver/esbuild.browser.mts b/extensions/vscode-test-resolver/esbuild.browser.mts new file mode 100644 index 0000000000000..29f504d6987a3 --- /dev/null +++ b/extensions/vscode-test-resolver/esbuild.browser.mts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +run({ + platform: 'browser', + entryPoints: { + 'testResolverMain': path.join(srcDir, 'extension.browser.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/debug-server-ready/extension.webpack.config.js b/extensions/vscode-test-resolver/esbuild.mts similarity index 51% rename from extensions/debug-server-ready/extension.webpack.config.js rename to extensions/vscode-test-resolver/esbuild.mts index 0c857b362f5da..2b75ca703da06 100644 --- a/extensions/debug-server-ready/extension.webpack.config.js +++ b/extensions/vscode-test-resolver/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts', +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), }, - resolve: { - mainFields: ['module', 'main'] - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/vscode-test-resolver/extension-browser.webpack.config.js b/extensions/vscode-test-resolver/extension-browser.webpack.config.js deleted file mode 100644 index 9aeb636997b86..0000000000000 --- a/extensions/vscode-test-resolver/extension-browser.webpack.config.js +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; - -export default withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.browser.ts' - }, - output: { - filename: 'testResolverMain.js' - } -}); diff --git a/extensions/vscode-test-resolver/tsconfig.browser.json b/extensions/vscode-test-resolver/tsconfig.browser.json new file mode 100644 index 0000000000000..790349e7fec3f --- /dev/null +++ b/extensions/vscode-test-resolver/tsconfig.browser.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ], + "files": [ + "./src/extension.browser.ts" + ] +} diff --git a/package-lock.json b/package-lock.json index 32c7949580296..33c4fa7383bc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,16 +30,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/headless": "^6.1.0-beta.165", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/headless": "^6.1.0-beta.167", + "@xterm/xterm": "^6.1.0-beta.167", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -84,6 +84,8 @@ "@types/yazl": "^2.4.2", "@typescript-eslint/utils": "^8.45.0", "@typescript/native-preview": "^7.0.0-dev.20260130", + "@vscode/component-explorer": "^0.1.1-10", + "@vscode/component-explorer-cli": "^0.1.1-6", "@vscode/gulp-electron": "https://github.com/microsoft/vscode-gulp-electron.git#405e3df0e4e9c37fcf549cbe6f5cef8d5ba5ddff", "@vscode/l10n-dev": "0.0.35", "@vscode/telemetry-extractor": "^1.10.2", @@ -1144,6 +1146,19 @@ "xtend": "~4.0.1" } }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1491,6 +1506,89 @@ "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2966,6 +3064,45 @@ "integrity": "sha512-5MfjQ+LBXnzLB/+nfpB8EpvHPdUkoW57cFcrIAHz52L/sBjwOxZER3+K2+nwb+/ejAiPmogTBDoJP/NM85uBtQ==", "license": "CC-BY-4.0" }, + "node_modules/@vscode/component-explorer": { + "version": "0.1.1-10", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-10.tgz", + "integrity": "sha512-Nokjk2DB1hgKeUL1FW5dHfXySgj17BgxcsiyzcG6etdFIbMpzv85nMQxrW/88aklgmJPrRVefMRHFYSds/F3/g==", + "dev": true, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@vscode/component-explorer-cli": { + "version": "0.1.1-6", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer-cli/-/component-explorer-cli-0.1.1-6.tgz", + "integrity": "sha512-OnypYKeBH8ZZh6++2NvVo9lPXFvHpIik6Y/KAa/UVMp4hI58KlQ0zEOWszvwR1i6mESn+BRWERFbQbNlKLec5g==", + "dev": true, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.26.0", + "clipanion": "^4.0.0-rc.4", + "express": "^5.0.0", + "zod": "^4.3.6" + }, + "bin": { + "component-explorer": "dist/index.js" + }, + "peerDependencies": { + "playwright": ">=1.40.0", + "vite": ">=5.0.0" + } + }, + "node_modules/@vscode/component-explorer-cli/node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@vscode/deviceid": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@vscode/deviceid/-/deviceid-0.1.4.tgz", @@ -3910,30 +4047,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz", - "integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.167.tgz", + "integrity": "sha512-+JSjagAk6okCaGVYFwkKl8qIBfy+W+h7p/qULIi9cC8QyeswOLaE4GOqY5yuGNQYU+zMlrpgR1ttyp0o6y9LHg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz", - "integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.167.tgz", + "integrity": "sha512-Bxi2oTaX7YM1gup0OSv02n9+tA3P1Ozlu5zyB/ZwSVkepB9FOxCODWD0l3DhWyLGMBqQ+OY/COw5SRxrKyvkNg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz", - "integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==", + "version": "0.11.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.167.tgz", + "integrity": "sha512-d+9ANnoz6D4J06CjronVolcG+J0jqUWQXbzciRqQkHq0or5k8PYuIj2DuuyBx/0rOaN7JYN347KQ9iylk+++xA==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -3943,7 +4080,7 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-ligatures/node_modules/lru-cache": { @@ -3965,63 +4102,63 @@ "license": "ISC" }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz", - "integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.167.tgz", + "integrity": "sha512-8eeaWnp0pnjYaKtOLsXVCE0hTFXS0A2kZCciWp52l6CbNGQsnky4VNWJXKaJrGbS+RHGxT6qWgcB+Mx5ETzZfg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz", - "integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==", + "version": "0.17.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.167.tgz", + "integrity": "sha512-1K6POdu0iCdjtW0Bs2z3IGWpMU4gJypbYxGnecbGnsH86rNRGwAKS0bKwWlHAiUQLlOxSGxTiNbazbrDln03FQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz", - "integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==", + "version": "0.15.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.167.tgz", + "integrity": "sha512-7EK/PN7QaUZcNE+bHmt7ELSNK3OBR2UZEuqNkE/0ooha7KqqI9mxZQG53Yn6wYcmRip34OFW9YF49kbTCkFuBg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz", - "integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.167.tgz", + "integrity": "sha512-uOJCfsMhML8GTesUKqCC4CH2cPH9yCIFnixiwgpcE5eLVrLszXW3tny25S/bu6EM+rfvE4nwIvLNTMrQYYnMFA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.164", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz", - "integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==", + "version": "0.20.0-beta.166", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.166.tgz", + "integrity": "sha512-SZmz7HDeSMc4O0++x14ma/UWbK/0Ea8AikHw6V5ex/shjrjwbik7Uf2n8FfG2zMYNgBakvCy/SbwDPtQN+IbRQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/headless": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.165.tgz", - "integrity": "sha512-GjAqUhEiY7gb12+yIItptgMKUwHMa7o39HpezD7sfNjYLjmvWQcB02jqUdVMsvjjAKTe2YJMXp3RkApeXdMRVg==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.167.tgz", + "integrity": "sha512-8TokXIwL8UeHhR4mAlUurzrqku5xaDXsikNi0HWpTcPCtZPdntxW36OaHxJmmpuHc8CecdaJehSuhApeW2TuZw==", "license": "MIT", "workspaces": [ "addons/*" ] }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz", - "integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.167.tgz", + "integrity": "sha512-OOG2gcH9OhEjY+KW3X2s30e1KzaRlynhkF9/oKfb2PNUJBYUdXeww4YAugrz7+nLP8KxCeOdSJrq7VvRzyZrwA==", "license": "MIT", "workspaces": [ "addons/*" @@ -4833,6 +4970,31 @@ "readable-stream": "^3.4.0" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -5093,6 +5255,23 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5394,6 +5573,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clipanion": { + "version": "4.0.0-rc.4", + "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-4.0.0-rc.4.tgz", + "integrity": "sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==", + "dev": true, + "license": "MIT", + "workspaces": [ + "website" + ], + "dependencies": { + "typanion": "^3.8.0" + }, + "peerDependencies": { + "typanion": "*" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -5765,6 +5960,16 @@ "node": ">= 0.6" } }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/cookies": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", @@ -5871,6 +6076,24 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6067,9 +6290,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7128,6 +7351,16 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -7162,6 +7395,29 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -7312,6 +7568,154 @@ "node": ">=0.10.0" } }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express-rate-limit/node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -7500,6 +7904,23 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -7608,6 +8029,28 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-parent-dir": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.1.tgz", @@ -7959,6 +8402,16 @@ "node": ">= 18" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -10191,6 +10644,16 @@ "node": ">=0.10.0" } }, + "node_modules/hono": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", + "integrity": "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -10405,6 +10868,23 @@ "node": ">=0.8.0" } }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -10556,6 +11036,16 @@ "node": ">= 12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -11136,6 +11626,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-base64": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", @@ -11265,6 +11765,13 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -11815,6 +12322,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -12215,6 +12735,19 @@ "node": ">= 0.10.0" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-options": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", @@ -13183,6 +13716,19 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -13889,6 +14435,16 @@ "node": ">=0.10.0" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -14270,6 +14826,20 @@ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -14361,6 +14931,22 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -14397,6 +14983,32 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -14425,6 +15037,33 @@ "integrity": "sha512-JkXJ0IrUcdupLoIx6gE4YcFaMVSGtu7kQf4NJoDJUnfBZGuATmJ2Yal2v55KTltp+WV8dGr7A0RtOzx6jmtM6Q==", "dev": true }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/read-package-json-fast": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", @@ -15027,6 +15666,30 @@ "node": ">=8.0" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -15073,12 +15736,29 @@ "ret": "~0.1.10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -15164,6 +15844,70 @@ "node": ">= 0.10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/serialize-error": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", @@ -15203,6 +15947,26 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -15340,6 +16104,82 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", @@ -17004,6 +17844,16 @@ "node": "*" } }, + "node_modules/typanion": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/typanion/-/typanion-3.14.0.tgz", + "integrity": "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==", + "dev": true, + "license": "MIT", + "workspaces": [ + "website" + ] + }, "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", @@ -17255,6 +18105,16 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -18276,6 +19136,16 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } } } } diff --git a/package.json b/package.json index df095bf4486f3..d4dda3bbce18e 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,9 @@ "extensions-ci": "npm run gulp extensions-ci", "extensions-ci-pr": "npm run gulp extensions-ci-pr", "perf": "node scripts/code-perf.js", - "update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run typecheck)" + "update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run typecheck)", + "install-local-component-explorer": "npm install ../vscode-packages/js-component-explorer/dist/vscode-component-explorer-0.1.0.tgz ../vscode-packages/js-component-explorer/dist/vscode-component-explorer-cli-0.1.0.tgz --no-save && cd build/vite && npm install ../../../vscode-packages/js-component-explorer/dist/vscode-component-explorer-vite-plugin-0.1.0.tgz --no-save", + "install-latest-component-explorer": "npm install @vscode/component-explorer@next @vscode/component-explorer-cli@next && cd build/vite && npm install @vscode/component-explorer-vite-plugin@next" }, "dependencies": { "@anthropic-ai/sandbox-runtime": "0.0.23", @@ -96,16 +98,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/headless": "^6.1.0-beta.165", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/headless": "^6.1.0-beta.167", + "@xterm/xterm": "^6.1.0-beta.167", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -150,6 +152,8 @@ "@types/yazl": "^2.4.2", "@typescript-eslint/utils": "^8.45.0", "@typescript/native-preview": "^7.0.0-dev.20260130", + "@vscode/component-explorer": "^0.1.1-10", + "@vscode/component-explorer-cli": "^0.1.1-6", "@vscode/gulp-electron": "https://github.com/microsoft/vscode-gulp-electron.git#405e3df0e4e9c37fcf549cbe6f5cef8d5ba5ddff", "@vscode/l10n-dev": "0.0.35", "@vscode/telemetry-extractor": "^1.10.2", @@ -245,4 +249,4 @@ "optionalDependencies": { "windows-foreground-love": "0.6.1" } -} \ No newline at end of file +} diff --git a/remote/package-lock.json b/remote/package-lock.json index d58a430653403..625b7143211ee 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -22,16 +22,16 @@ "@vscode/vscode-languagedetection": "1.0.23", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/headless": "^6.1.0-beta.165", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/headless": "^6.1.0-beta.167", + "@xterm/xterm": "^6.1.0-beta.167", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -578,30 +578,30 @@ "license": "MIT" }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz", - "integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.167.tgz", + "integrity": "sha512-+JSjagAk6okCaGVYFwkKl8qIBfy+W+h7p/qULIi9cC8QyeswOLaE4GOqY5yuGNQYU+zMlrpgR1ttyp0o6y9LHg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz", - "integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.167.tgz", + "integrity": "sha512-Bxi2oTaX7YM1gup0OSv02n9+tA3P1Ozlu5zyB/ZwSVkepB9FOxCODWD0l3DhWyLGMBqQ+OY/COw5SRxrKyvkNg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz", - "integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==", + "version": "0.11.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.167.tgz", + "integrity": "sha512-d+9ANnoz6D4J06CjronVolcG+J0jqUWQXbzciRqQkHq0or5k8PYuIj2DuuyBx/0rOaN7JYN347KQ9iylk+++xA==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -611,67 +611,67 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz", - "integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.167.tgz", + "integrity": "sha512-8eeaWnp0pnjYaKtOLsXVCE0hTFXS0A2kZCciWp52l6CbNGQsnky4VNWJXKaJrGbS+RHGxT6qWgcB+Mx5ETzZfg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz", - "integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==", + "version": "0.17.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.167.tgz", + "integrity": "sha512-1K6POdu0iCdjtW0Bs2z3IGWpMU4gJypbYxGnecbGnsH86rNRGwAKS0bKwWlHAiUQLlOxSGxTiNbazbrDln03FQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz", - "integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==", + "version": "0.15.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.167.tgz", + "integrity": "sha512-7EK/PN7QaUZcNE+bHmt7ELSNK3OBR2UZEuqNkE/0ooha7KqqI9mxZQG53Yn6wYcmRip34OFW9YF49kbTCkFuBg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz", - "integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.167.tgz", + "integrity": "sha512-uOJCfsMhML8GTesUKqCC4CH2cPH9yCIFnixiwgpcE5eLVrLszXW3tny25S/bu6EM+rfvE4nwIvLNTMrQYYnMFA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.164", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz", - "integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==", + "version": "0.20.0-beta.166", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.166.tgz", + "integrity": "sha512-SZmz7HDeSMc4O0++x14ma/UWbK/0Ea8AikHw6V5ex/shjrjwbik7Uf2n8FfG2zMYNgBakvCy/SbwDPtQN+IbRQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/headless": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.165.tgz", - "integrity": "sha512-GjAqUhEiY7gb12+yIItptgMKUwHMa7o39HpezD7sfNjYLjmvWQcB02jqUdVMsvjjAKTe2YJMXp3RkApeXdMRVg==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.167.tgz", + "integrity": "sha512-8TokXIwL8UeHhR4mAlUurzrqku5xaDXsikNi0HWpTcPCtZPdntxW36OaHxJmmpuHc8CecdaJehSuhApeW2TuZw==", "license": "MIT", "workspaces": [ "addons/*" ] }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz", - "integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.167.tgz", + "integrity": "sha512-OOG2gcH9OhEjY+KW3X2s30e1KzaRlynhkF9/oKfb2PNUJBYUdXeww4YAugrz7+nLP8KxCeOdSJrq7VvRzyZrwA==", "license": "MIT", "workspaces": [ "addons/*" diff --git a/remote/package.json b/remote/package.json index 0784e097baf34..ecc25ae5590e0 100644 --- a/remote/package.json +++ b/remote/package.json @@ -17,16 +17,16 @@ "@vscode/vscode-languagedetection": "1.0.23", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/headless": "^6.1.0-beta.165", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/headless": "^6.1.0-beta.167", + "@xterm/xterm": "^6.1.0-beta.167", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index 93e41b4397009..8ee7ddeb9bd32 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -14,15 +14,15 @@ "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.23", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/xterm": "^6.1.0-beta.167", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client": "0.3.1", @@ -100,30 +100,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz", - "integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.167.tgz", + "integrity": "sha512-+JSjagAk6okCaGVYFwkKl8qIBfy+W+h7p/qULIi9cC8QyeswOLaE4GOqY5yuGNQYU+zMlrpgR1ttyp0o6y9LHg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz", - "integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.167.tgz", + "integrity": "sha512-Bxi2oTaX7YM1gup0OSv02n9+tA3P1Ozlu5zyB/ZwSVkepB9FOxCODWD0l3DhWyLGMBqQ+OY/COw5SRxrKyvkNg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz", - "integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==", + "version": "0.11.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.167.tgz", + "integrity": "sha512-d+9ANnoz6D4J06CjronVolcG+J0jqUWQXbzciRqQkHq0or5k8PYuIj2DuuyBx/0rOaN7JYN347KQ9iylk+++xA==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -133,58 +133,58 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz", - "integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.167.tgz", + "integrity": "sha512-8eeaWnp0pnjYaKtOLsXVCE0hTFXS0A2kZCciWp52l6CbNGQsnky4VNWJXKaJrGbS+RHGxT6qWgcB+Mx5ETzZfg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz", - "integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==", + "version": "0.17.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.167.tgz", + "integrity": "sha512-1K6POdu0iCdjtW0Bs2z3IGWpMU4gJypbYxGnecbGnsH86rNRGwAKS0bKwWlHAiUQLlOxSGxTiNbazbrDln03FQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz", - "integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==", + "version": "0.15.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.167.tgz", + "integrity": "sha512-7EK/PN7QaUZcNE+bHmt7ELSNK3OBR2UZEuqNkE/0ooha7KqqI9mxZQG53Yn6wYcmRip34OFW9YF49kbTCkFuBg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz", - "integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.167.tgz", + "integrity": "sha512-uOJCfsMhML8GTesUKqCC4CH2cPH9yCIFnixiwgpcE5eLVrLszXW3tny25S/bu6EM+rfvE4nwIvLNTMrQYYnMFA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.164", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz", - "integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==", + "version": "0.20.0-beta.166", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.166.tgz", + "integrity": "sha512-SZmz7HDeSMc4O0++x14ma/UWbK/0Ea8AikHw6V5ex/shjrjwbik7Uf2n8FfG2zMYNgBakvCy/SbwDPtQN+IbRQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz", - "integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.167.tgz", + "integrity": "sha512-OOG2gcH9OhEjY+KW3X2s30e1KzaRlynhkF9/oKfb2PNUJBYUdXeww4YAugrz7+nLP8KxCeOdSJrq7VvRzyZrwA==", "license": "MIT", "workspaces": [ "addons/*" diff --git a/remote/web/package.json b/remote/web/package.json index eb80319e29b22..37b14b0114ffd 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -9,15 +9,15 @@ "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.23", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/xterm": "^6.1.0-beta.167", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client": "0.3.1", diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index a369fb8187156..254bb04f46cf0 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -8,6 +8,7 @@ import { getProgressAccessibilitySignalScheduler } from './progressAccessibility import { RunOnceScheduler } from '../../../common/async.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../common/lifecycle.js'; import { isNumber } from '../../../common/types.js'; +import { localize } from '../../../../nls.js'; import './progressbar.css'; const CSS_DONE = 'done'; @@ -15,8 +16,10 @@ const CSS_ACTIVE = 'active'; const CSS_INFINITE = 'infinite'; const CSS_INFINITE_LONG_RUNNING = 'infinite-long-running'; const CSS_DISCRETE = 'discrete'; +const NLS_PROGRESS_LABEL = localize('progress', "Progress"); export interface IProgressBarOptions extends IProgressBarStyles { + ariaLabel?: string; } export interface IProgressBarStyles { @@ -68,6 +71,7 @@ export class ProgressBar extends Disposable { this.element.classList.add('monaco-progress-container'); this.element.setAttribute('role', 'progressbar'); this.element.setAttribute('aria-valuemin', '0'); + this.element.setAttribute('aria-label', options?.ariaLabel && options.ariaLabel.trim() ? options.ariaLabel : NLS_PROGRESS_LABEL); container.appendChild(this.element); this.bit = document.createElement('div'); diff --git a/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts index 0247519b550dc..45473f77c4dec 100644 --- a/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts +++ b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts @@ -8,6 +8,7 @@ import { Disposable, markAsSingleton } from '../../../base/common/lifecycle.js'; import { RGBA8 } from '../core/misc/rgba.js'; import { TokenizationRegistry } from '../languages.js'; import { ColorId } from '../encodedTokenAttributes.js'; +import { BugIndicatingError, onUnexpectedError } from '../../../base/common/errors.js'; export class MinimapTokensColorTracker extends Disposable { private static _INSTANCE: MinimapTokensColorTracker | null = null; @@ -57,7 +58,12 @@ export class MinimapTokensColorTracker extends Disposable { // background color (basically invisible) colorId = ColorId.DefaultBackground; } - return this._colors[colorId]; + let color = this._colors[colorId]; + if (!color) { + onUnexpectedError(new BugIndicatingError(`Missing color for colorId ${colorId}`)); + color = RGBA8.Empty; + } + return color; } public backgroundIsLight(): boolean { diff --git a/src/vs/sessions/LAYOUT.md b/src/vs/sessions/LAYOUT.md index 1448a8a3dca49..7cd6cc059bade 100644 --- a/src/vs/sessions/LAYOUT.md +++ b/src/vs/sessions/LAYOUT.md @@ -27,20 +27,9 @@ The Agent Sessions Workbench (`Workbench` in `sessions/browser/workbench.ts`) pr │ │ Panel │ └─────────┴───────────────────────────────────────────────────────┘ - ┌───────────────────────────────────────┐ - │ ╔═══════════════════════════╗ │ - │ ║ Editor Modal Overlay ║ │ - │ ║ ┌─────────────────────┐ ║ │ - │ ║ │ [header] [X] │ ║ │ - │ ║ ├─────────────────────┤ ║ │ - │ ║ │ │ ║ │ - │ ║ │ Editor Part │ ║ │ - │ ║ │ │ ║ │ - │ ║ │ │ ║ │ - │ ║ └─────────────────────┘ ║ │ - │ ╚═══════════════════════════╝ │ - └───────────────────────────────────────┘ - (shown when editors are open) +Editors open via MODAL_GROUP into the standard ModalEditorPart overlay +(created on-demand by EditorParts.createModalEditorPart). The main +editor part exists but is hidden (display:none) for future use. ``` ### 2.2 Parts @@ -52,7 +41,7 @@ The Agent Sessions Workbench (`Workbench` in `sessions/browser/workbench.ts`) pr | Titlebar | `Parts.TITLEBAR_PART` | Top of right section | Always visible | — | | Sidebar | `Parts.SIDEBAR_PART` | Left, spans full height from top to bottom | Visible | `ViewContainerLocation.Sidebar` | | Chat Bar | `Parts.CHATBAR_PART` | Top-right section, takes remaining width | Visible | `ViewContainerLocation.ChatBar` | -| Editor | `Parts.EDITOR_PART` | **Modal overlay** (not in grid) | Hidden | — | +| Editor | `Parts.EDITOR_PART` | Hidden main part (not in grid); editors open via `MODAL_GROUP` into `ModalEditorPart` overlay | Hidden | — | | Auxiliary Bar | `Parts.AUXILIARYBAR_PART` | Top-right section, right side | Visible | `ViewContainerLocation.AuxiliaryBar` | | Panel | `Parts.PANEL_PART` | Below Chat Bar and Auxiliary Bar (right section only) | Hidden | `ViewContainerLocation.Panel` | @@ -180,87 +169,37 @@ This structure places the sidebar at the root level spanning the full window hei | Sidebar | 300px width | | Auxiliary Bar | 300px width | | Chat Bar | Remaining space | -| Editor Modal | 80% of workbench (min 400x300, max 1200x900), calculated in TypeScript | | Panel | 300px height | | Titlebar | Determined by `minimumHeight` (~30px) | ### 4.3 Editor Modal -The Editor part is rendered as a **modal overlay** rather than being part of the grid. This provides a focused editing experience that hovers above the main workbench layout. +The main editor part is created but hidden (`display:none`). It exists for future use but is not currently visible. All editors are forced to open in the `ModalEditorPart` overlay via the standard `createModalEditorPart()` mechanism. -#### Modal Structure +#### How It Works -``` -EditorModal -├── Overlay (semi-transparent backdrop) -├── Container (centered dialog) -│ ├── Header (32px, contains close button) -│ └── Content (editor part fills remaining space) -``` +The sessions configuration sets `workbench.editor.useModal` to `'on'` (in `contrib/configuration/browser/configuration.contribution.ts`). This causes `findGroup()` in `editorGroupFinder.ts` to redirect all editor opens (that do not specify an explicit preferred group) to `createModalEditorPart()`, which creates the standard workbench `ModalEditorPart` overlay on-demand. + +When the setting is `'on'`: +- All editors without an explicit preferred group open in the modal editor part +- The modal is not auto-closed when editors open without explicit `MODAL_GROUP` as preferred group #### Behavior | Trigger | Action | |---------|--------| -| Editor opens (`onWillOpenEditor`) | Modal shows automatically | -| All editors close | Modal hides automatically | +| Any editor opens (no explicit group) | `ModalEditorPart` overlay created/reused automatically | +| All editors closed in modal | Modal closes and is disposed | | Click backdrop | Close all editors, hide modal | -| Click close button (X) | Close all editors, hide modal | -| Press Escape key | Close all editors, hide modal | - -#### Modal Sizing +| Press Escape | Close all editors, hide modal | -Modal dimensions are calculated in TypeScript rather than CSS. The `EditorModal.layout()` method receives workbench dimensions and computes the modal size with constraints: - -| Property | Value | Constant | -|----------|-------|----------| -| Size Percentage | 80% of workbench | `MODAL_SIZE_PERCENTAGE = 0.8` | -| Max Width | 1200px | `MODAL_MAX_WIDTH = 1200` | -| Max Height | 900px | `MODAL_MAX_HEIGHT = 900` | -| Min Width | 400px | `MODAL_MIN_WIDTH = 400` | -| Min Height | 300px | `MODAL_MIN_HEIGHT = 300` | -| Header Height | 32px | `MODAL_HEADER_HEIGHT = 32` | - -The calculation: -```typescript -modalWidth = min(MODAL_MAX_WIDTH, max(MODAL_MIN_WIDTH, workbenchWidth * MODAL_SIZE_PERCENTAGE)) -modalHeight = min(MODAL_MAX_HEIGHT, max(MODAL_MIN_HEIGHT, workbenchHeight * MODAL_SIZE_PERCENTAGE)) -contentHeight = modalHeight - MODAL_HEADER_HEIGHT -``` +#### Configuration -#### CSS Classes - -| Class | Applied To | Notes | -|-------|------------|-------| -| `editor-modal-overlay` | Overlay container | Positioned absolute, full size | -| `editor-modal-overlay.visible` | When modal is shown | Enables pointer events | -| `editor-modal-backdrop` | Semi-transparent backdrop | Clicking closes modal | -| `editor-modal-container` | Centered modal dialog | Width/height set in TypeScript | -| `editor-modal-header` | Header with close button | Fixed 32px height | -| `editor-modal-content` | Editor content area | Width/height set in TypeScript | -| `editor-modal-visible` | Added to `mainContainer` when modal is visible | — | - -#### Implementation - -The modal is implemented in `EditorModal` class (`parts/editorModal.ts`): - -```typescript -class EditorModal extends Disposable { - // Events - readonly onDidChangeVisibility: Event; - - // State - get visible(): boolean; - - // Methods - show(): void; // Show modal using stored dimensions - hide(): void; // Hide modal - close(): void; // Close all editors, then hide - layout(workbenchWidth: number, workbenchHeight: number): void; // Store dimensions, re-layout if visible -} -``` +The setting `workbench.editor.useModal` is an enum with three values: +- `'off'`: Editors never open in a modal overlay +- `'default'`: Certain editors (e.g. Settings, Keyboard Shortcuts) may open in a modal overlay when requested via `MODAL_GROUP` +- `'on'`: All editors open in a modal overlay (used by sessions window) -The `Workbench.layout()` passes the workbench dimensions to `EditorModal.layout()`, which calculates and applies the modal size with min/max constraints. Dimensions are stored so that `show()` can use them when the modal becomes visible. --- @@ -302,9 +241,9 @@ setPartHidden(hidden: boolean, part: Parts): void - Showing a part restores the last active pane composite - **Panel Part:** - If the panel is maximized when hiding, it exits maximized state first -- **Editor Part Auto-Visibility:** - - Automatically shows when an editor is about to open (`onWillOpenEditor`) - - Automatically hides when the last editor closes (`onDidCloseEditor` + all groups empty) +- **Editor Part:** + - The main editor part is always hidden (`display:none`); `setEditorHidden()` is a no-op + - All editors open via `MODAL_GROUP` into the `ModalEditorPart` overlay, which manages its own lifecycle ### 6.2 Part Sizing @@ -386,11 +325,10 @@ Applied to `mainContainer` based on part visibility: | Class | Applied When | |-------|--------------| | `nosidebar` | Sidebar is hidden | -| `nomaineditorarea` | Editor modal is hidden | +| `nomaineditorarea` | Editor part is hidden (always applied — main editor part is permanently hidden) | | `noauxiliarybar` | Auxiliary bar is hidden | | `nochatbar` | Chat bar is hidden | | `nopanel` | Panel is hidden | -| `editor-modal-visible` | Editor modal is visible | ### 8.2 Window State Classes @@ -424,7 +362,6 @@ The Agent Sessions workbench uses specialized part implementations that extend t | Chat Bar | `ChatBarPart` | `AbstractPaneCompositePart` | `sessions/browser/parts/chatBarPart.ts` | | Titlebar | `TitlebarPart` / `MainTitlebarPart` | `Part` | `sessions/browser/parts/titlebarPart.ts` | | Project Bar | `ProjectBarPart` | `Part` | `sessions/browser/parts/projectBarPart.ts` | -| Editor Modal | `EditorModal` | `Disposable` | `sessions/browser/parts/editorModal.ts` | ### 9.2 Key Differences from Standard Parts @@ -555,7 +492,7 @@ src/vs/sessions/ │ ├── menus.ts # Agent sessions menu IDs (Menus export) │ ├── layoutActions.ts # Layout actions (toggle sidebar, secondary sidebar, panel) │ ├── paneCompositePartService.ts # AgenticPaneCompositePartService -│ ├── style.css # Layout-specific styles (including editor modal) +│ ├── style.css # Layout-specific styles │ ├── widget/ # Agent sessions chat widget │ │ ├── AGENTS_CHAT_WIDGET.md # Chat widget architecture documentation │ │ ├── agentSessionsChatWidget.ts # Main chat widget wrapper @@ -570,7 +507,6 @@ src/vs/sessions/ │ ├── panelPart.ts # Agent session panel │ ├── chatBarPart.ts # Chat Bar part implementation │ ├── projectBarPart.ts # Project bar part (folder entries, icon customization) -│ ├── editorModal.ts # Editor modal overlay implementation │ ├── parts.ts # AgenticParts enum │ ├── agentSessionsChatInputPart.ts # Chat input part adapter │ ├── agentSessionsChatWelcomePart.ts # Chat welcome part @@ -635,8 +571,8 @@ When modifying the Agent Sessions layout: 1. `constructor()` — Register error handlers 2. `startup()` — Initialize services and layout 3. `initServices()` — Set up service collection (including `TitleService`), register singleton services, set lifecycle to `Ready` -4. `initLayout()` — Get services, register layout listeners, register editor open/close listeners -5. `renderWorkbench()` — Create DOM, create parts, create editor modal, set up notifications +4. `initLayout()` — Get services, register layout listeners +5. `renderWorkbench()` — Create DOM, create parts, create hidden editor part, set up notifications 6. `createWorkbenchLayout()` — Build the grid structure 7. `createWorkbenchManagement()` — (No-op in agent sessions layout) 8. `layout()` — Perform initial layout @@ -704,6 +640,7 @@ interface IPartVisibilityState { | Date | Change | |------|--------| +| 2026-02-20 | Replaced custom `EditorModal` with standard `ModalEditorPart` via `MODAL_GROUP`; main editor part created but hidden; changed `workbench.editor.useModal` from boolean to enum (`off`/`default`/`on`); sessions config uses `on`; removed `editorModal.ts` and editor modal CSS | | 2026-02-17 | Added `-webkit-app-region: drag` to sidebar title area so it can be used to drag the window; interactive children (actions, composite bar, labels) marked `no-drag`; CSS rules scoped to `.agent-sessions-workbench` in `parts/media/sidebarPart.css` | | 2026-02-13 | Documentation sync: Updated all file names, class names, and references to match current implementation. `AgenticWorkbench` → `Workbench`, `AgenticSidebarPart` → `SidebarPart`, `AgenticAuxiliaryBarPart` → `AuxiliaryBarPart`, `AgenticPanelPart` → `PanelPart`, `agenticWorkbench.ts` → `workbench.ts`, `agenticWorkbenchMenus.ts` → `menus.ts`, `agenticLayoutActions.ts` → `layoutActions.ts`, `AgenticTitleBarWidget` → `SessionsTitleBarWidget`, `AgenticTitleBarContribution` → `SessionsTitleBarContribution`. Removed references to deleted files (`sidebarRevealButton.ts`, `floatingToolbar.ts`, `agentic.contributions.ts`, `agenticTitleBarWidget.ts`). Updated pane composite architecture from `SyncDescriptor`-based to `AgenticPaneCompositePartService`. Moved account widget docs from titlebar to sidebar footer. Added documentation for sidebar footer, project bar, traffic light spacer, card appearance styling, widget directory, and new contrib structure (`accountMenu/`, `chat/`, `configuration/`, `sessions/`). Updated titlebar actions to reflect Run Script split button and Open submenu. Removed Toggle Maximize panel action (no longer registered). Updated contributions section with all current contributions and their locations. | | 2026-02-13 | Changed grid structure: sidebar now spans full window height at root level (HORIZONTAL root orientation); Titlebar moved inside right section; Grid is now `Sidebar \| [Titlebar / TopRight / Panel]` instead of `Titlebar / [Sidebar \| RightSection]`; Panel maximize now excludes both titlebar and sidebar; Floating toolbar positioning no longer depends on titlebar height | diff --git a/src/vs/sessions/README.md b/src/vs/sessions/README.md index 002781a49ce4d..d03cecc3dd160 100644 --- a/src/vs/sessions/README.md +++ b/src/vs/sessions/README.md @@ -65,7 +65,6 @@ src/vs/sessions/ │ ├── panelPart.ts ← Panel part │ ├── chatBarPart.ts ← Chat bar part │ ├── projectBarPart.ts ← Project bar part (folder entries) -│ ├── editorModal.ts ← Editor modal overlay │ ├── parts.ts ← AgenticParts enum │ ├── agentSessionsChatInputPart.ts ← Chat input part adapter │ ├── agentSessionsChatWelcomePart.ts ← Chat welcome part diff --git a/src/vs/sessions/browser/layoutActions.ts b/src/vs/sessions/browser/layoutActions.ts index d16de091c627c..7d1a013328a7e 100644 --- a/src/vs/sessions/browser/layoutActions.ts +++ b/src/vs/sessions/browser/layoutActions.ts @@ -13,7 +13,7 @@ import { Menus } from './menus.js'; import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../platform/keybinding/common/keybindingsRegistry.js'; import { registerIcon } from '../../platform/theme/common/iconRegistry.js'; -import { AuxiliaryBarVisibleContext, SideBarVisibleContext } from '../../workbench/common/contextkeys.js'; +import { AuxiliaryBarVisibleContext, IsAuxiliaryWindowContext, SideBarVisibleContext } from '../../workbench/common/contextkeys.js'; import { IWorkbenchLayoutService, Parts } from '../../workbench/services/layout/browser/layoutService.js'; // Register Icons @@ -52,7 +52,8 @@ class ToggleSidebarVisibilityAction extends Action2 { { id: Menus.TitleBarLeft, group: 'navigation', - order: 0 + order: 0, + when: IsAuxiliaryWindowContext.toNegated() } ] }); @@ -97,7 +98,8 @@ class ToggleSecondarySidebarVisibilityAction extends Action2 { { id: Menus.TitleBarRight, group: 'navigation', - order: 10 + order: 10, + when: IsAuxiliaryWindowContext.toNegated() } ] }); @@ -132,7 +134,8 @@ class TogglePanelVisibilityAction extends Action2 { { id: Menus.PanelTitle, group: 'navigation', - order: 2 + order: 2, + when: IsAuxiliaryWindowContext.toNegated() } ] }); diff --git a/src/vs/sessions/browser/parts/editorModal.ts b/src/vs/sessions/browser/parts/editorModal.ts deleted file mode 100644 index 6b30966f5623c..0000000000000 --- a/src/vs/sessions/browser/parts/editorModal.ts +++ /dev/null @@ -1,194 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $ } from '../../../base/browser/dom.js'; -import { mainWindow } from '../../../base/browser/window.js'; -import { Disposable } from '../../../base/common/lifecycle.js'; -import { Emitter, Event } from '../../../base/common/event.js'; -import { Codicon } from '../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../base/common/themables.js'; -import { Part } from '../../../workbench/browser/part.js'; -import { Parts } from '../../../workbench/services/layout/browser/layoutService.js'; -import { IEditorGroupsService } from '../../../workbench/services/editor/common/editorGroupsService.js'; -import { mark } from '../../../base/common/performance.js'; - -const MODAL_HEADER_HEIGHT = 32; -const MODAL_SIZE_PERCENTAGE = 0.8; -const MODAL_MIN_WIDTH = 400; -const MODAL_MAX_WIDTH = 1200; -const MODAL_MIN_HEIGHT = 300; -const MODAL_MAX_HEIGHT = 900; - -export class EditorModal extends Disposable { - - private readonly _onDidChangeVisibility = this._register(new Emitter()); - readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - - private readonly overlay: HTMLElement; - private readonly container: HTMLElement; - private readonly content: HTMLElement; - - private _visible = false; - get visible(): boolean { return this._visible; } - - private _workbenchWidth = 0; - private _workbenchHeight = 0; - - constructor( - private readonly parentContainer: HTMLElement, - private readonly editorPart: Part, - private readonly editorGroupService: IEditorGroupsService - ) { - super(); - - // Create modal structure - this.overlay = this.createOverlay(); - this.container = this.createContainer(); - this.content = this.createContent(); - - // Assemble the modal - this.container.appendChild(this.content); - this.overlay.appendChild(this.container); - - // Create and add editor part to modal content - this.createEditorPart(); - - // Register keyboard handler - this.registerKeyboardHandler(); - - // Add to parent - this.parentContainer.appendChild(this.overlay); - } - - private createOverlay(): HTMLElement { - const overlay = $('div.editor-modal-overlay'); - - // Create backdrop (clicking closes the modal) - const backdrop = $('div.editor-modal-backdrop'); - backdrop.addEventListener('click', () => this.close()); - overlay.appendChild(backdrop); - - return overlay; - } - - private createContainer(): HTMLElement { - const container = $('div.editor-modal-container'); - container.setAttribute('role', 'dialog'); - container.setAttribute('aria-modal', 'true'); - - // Create header with close button - const header = $('div.editor-modal-header'); - const closeButton = $('button.editor-modal-close-button'); - closeButton.setAttribute('aria-label', 'Close'); - closeButton.title = 'Close (Escape)'; - const closeIcon = $('span'); - closeIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.close)); - closeButton.appendChild(closeIcon); - closeButton.addEventListener('click', () => this.close()); - header.appendChild(closeButton); - container.appendChild(header); - - return container; - } - - private createContent(): HTMLElement { - return $('div.editor-modal-content'); - } - - private createEditorPart(): void { - const editorPartContainer = document.createElement('div'); - editorPartContainer.classList.add('part', 'editor'); - editorPartContainer.id = Parts.EDITOR_PART; - editorPartContainer.setAttribute('role', 'main'); - - mark('code/willCreatePart/workbench.parts.editor'); - this.editorPart.create(editorPartContainer, { restorePreviousState: false }); - mark('code/didCreatePart/workbench.parts.editor'); - - this.content.appendChild(editorPartContainer); - } - - private registerKeyboardHandler(): void { - mainWindow.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && this._visible) { - this.close(); - } - }); - } - - show(): void { - if (this._visible) { - return; - } - - this._visible = true; - this.overlay.classList.add('visible'); - - this.doLayout(); - - this._onDidChangeVisibility.fire(true); - } - - hide(): void { - if (!this._visible) { - return; - } - - this._visible = false; - this.overlay.classList.remove('visible'); - - this._onDidChangeVisibility.fire(false); - } - - close(): void { - if (!this._visible) { - return; - } - - // Close all editors in all groups - for (const group of this.editorGroupService.groups) { - group.closeAllEditors(); - } - - // Hide the modal - this.hide(); - } - - layout(workbenchWidth: number, workbenchHeight: number): void { - this._workbenchWidth = workbenchWidth; - this._workbenchHeight = workbenchHeight; - - if (this._visible) { - this.doLayout(); - } - } - - private doLayout(): void { - // Calculate modal dimensions based on workbench size with constraints - const modalWidth = Math.floor( - Math.min(MODAL_MAX_WIDTH, Math.max(MODAL_MIN_WIDTH, this._workbenchWidth * MODAL_SIZE_PERCENTAGE)) - ); - const modalHeight = Math.floor( - Math.min(MODAL_MAX_HEIGHT, Math.max(MODAL_MIN_HEIGHT, this._workbenchHeight * MODAL_SIZE_PERCENTAGE)) - ); - - // Set the modal container dimensions - this.container.style.width = `${modalWidth}px`; - this.container.style.height = `${modalHeight}px`; - - // Calculate content dimensions (subtract header height) - const contentWidth = modalWidth; - const contentHeight = modalHeight - MODAL_HEADER_HEIGHT; - - if (contentWidth > 0 && contentHeight > 0) { - // Explicitly size the content area - this.content.style.width = `${contentWidth}px`; - this.content.style.height = `${contentHeight}px`; - - // Layout the editor part - this.editorPart.layout(contentWidth, contentHeight, 0, 0); - } - } -} diff --git a/src/vs/sessions/browser/style.css b/src/vs/sessions/browser/style.css index aa371216552a2..ac055f6948e6e 100644 --- a/src/vs/sessions/browser/style.css +++ b/src/vs/sessions/browser/style.css @@ -48,112 +48,6 @@ background-color: var(--vscode-sideBar-background); } -/* Editor Modal Overlay */ -.agent-sessions-workbench .editor-modal-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1000; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - visibility: hidden; -} - -.agent-sessions-workbench .editor-modal-overlay.visible { - pointer-events: auto; - visibility: visible; -} - -/* Modal Backdrop */ -.agent-sessions-workbench .editor-modal-backdrop { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.4); - opacity: 0; - transition: opacity 0.15s ease-out; -} - -.agent-sessions-workbench .editor-modal-overlay.visible .editor-modal-backdrop { - opacity: 1; -} - -/* Modal Container */ -.agent-sessions-workbench .editor-modal-container { - position: relative; - display: flex; - flex-direction: column; - /* Width and height are set dynamically in TypeScript */ - background-color: var(--vscode-editor-background); - border: 1px solid var(--vscode-editorWidget-border, var(--vscode-contrastBorder)); - border-radius: 8px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); - overflow: hidden; - transform: scale(0.95); - opacity: 0; - transition: transform 0.15s ease-out, opacity 0.15s ease-out; -} - -.agent-sessions-workbench .editor-modal-overlay.visible .editor-modal-container { - transform: scale(1); - opacity: 1; -} - -/* Modal Header with close button */ -.agent-sessions-workbench .editor-modal-header { - display: flex; - align-items: center; - justify-content: flex-end; - height: 32px; - min-height: 32px; - padding: 0 8px; - background-color: var(--vscode-editorGroupHeader-tabsBackground); - border-bottom: 1px solid var(--vscode-editorGroupHeader-tabsBorder, transparent); -} - -.agent-sessions-workbench .editor-modal-close-button { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border: none; - background: transparent; - color: var(--vscode-icon-foreground); - cursor: pointer; - border-radius: 4px; -} - -.agent-sessions-workbench .editor-modal-close-button:hover { - background-color: var(--vscode-toolbar-hoverBackground); -} - -.agent-sessions-workbench .editor-modal-close-button:active { - background-color: var(--vscode-toolbar-activeBackground); -} - -/* Editor Content Area */ -.agent-sessions-workbench .editor-modal-content { - flex: 1; - overflow: hidden; - position: relative; - min-height: 0; /* Allow flexbox shrinking */ -} - -.agent-sessions-workbench .editor-modal-content > .part.editor { - position: absolute !important; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - /* ---- Chat Input ---- */ .agent-sessions-workbench .interactive-session .chat-input-container { diff --git a/src/vs/sessions/browser/workbench.ts b/src/vs/sessions/browser/workbench.ts index 3ba1a8bb2cc0c..657ad48a44aa3 100644 --- a/src/vs/sessions/browser/workbench.ts +++ b/src/vs/sessions/browser/workbench.ts @@ -59,7 +59,6 @@ import { registerNotificationCommands } from '../../workbench/browser/parts/noti import { NotificationsToasts } from '../../workbench/browser/parts/notifications/notificationsToasts.js'; import { IMarkdownRendererService } from '../../platform/markdown/browser/markdownRenderer.js'; import { EditorMarkdownCodeBlockRenderer } from '../../editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.js'; -import { EditorModal } from './parts/editorModal.js'; import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js'; import { TitleService } from './parts/titlebarPart.js'; @@ -83,8 +82,7 @@ enum LayoutClasses { AUXILIARYBAR_HIDDEN = 'noauxiliarybar', CHATBAR_HIDDEN = 'nochatbar', FULLSCREEN = 'fullscreen', - MAXIMIZED = 'maximized', - EDITOR_MODAL_VISIBLE = 'editor-modal-visible' + MAXIMIZED = 'maximized' } //#endregion @@ -230,8 +228,6 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { private panelPartView!: ISerializableView; private auxiliaryBarPartView!: ISerializableView; - // Editor modal - private editorModal!: EditorModal; private chatBarPartView!: ISerializableView; private readonly partVisibility: IPartVisibilityState = { @@ -545,8 +541,8 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { mark(`code/didCreatePart/${id}`); } - // Create Editor Part in modal - this.createEditorModal(); + // Create Editor Part (hidden — all editors open via MODAL_GROUP) + this.createHiddenEditorPart(); // Notification Handlers this.createNotificationsHandlers(instantiationService, notificationService); @@ -592,13 +588,18 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { return part; } - private createEditorModal(): void { - const editorPart = this.getPart(Parts.EDITOR_PART); - this.editorModal = this._register(new EditorModal( - this.mainContainer, - editorPart, - this.editorGroupService - )); + private createHiddenEditorPart(): void { + const editorPartContainer = document.createElement('div'); + editorPartContainer.classList.add('part', 'editor'); + editorPartContainer.id = Parts.EDITOR_PART; + editorPartContainer.setAttribute('role', 'main'); + editorPartContainer.style.display = 'none'; + + mark('code/willCreatePart/workbench.parts.editor'); + this.getPart(Parts.EDITOR_PART).create(editorPartContainer, { restorePreviousState: false }); + mark('code/didCreatePart/workbench.parts.editor'); + + this.mainContainer.appendChild(editorPartContainer); } private restore(lifecycleService: ILifecycleService): void { @@ -880,9 +881,6 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { // Layout the grid widget this.workbenchGrid.layout(this._mainContainerDimension.width, this._mainContainerDimension.height); - // Layout the editor modal with workbench dimensions - this.editorModal.layout(this._mainContainerDimension.width, this._mainContainerDimension.height); - // Emit as event this.handleContainerDidLayout(this.mainContainer, this._mainContainerDimension); } @@ -1106,14 +1104,6 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { this.partVisibility.editor = !hidden; this.mainContainer.classList.toggle(LayoutClasses.MAIN_EDITOR_AREA_HIDDEN, hidden); - this.mainContainer.classList.toggle(LayoutClasses.EDITOR_MODAL_VISIBLE, !hidden); - - // Show/hide modal - if (hidden) { - this.editorModal.hide(); - } else { - this.editorModal.show(); - } } private setPanelHidden(hidden: boolean): void { diff --git a/src/vs/sessions/contrib/changesView/browser/changesView.ts b/src/vs/sessions/contrib/changesView/browser/changesView.ts index a5e7e3e70a876..973e8e2ba1b4b 100644 --- a/src/vs/sessions/contrib/changesView/browser/changesView.ts +++ b/src/vs/sessions/contrib/changesView/browser/changesView.ts @@ -52,7 +52,7 @@ import { chatEditingWidgetFileStateContextKey, hasAppliedChatEditsContextKey, ha import { getChatSessionType } from '../../../../workbench/contrib/chat/common/model/chatUri.js'; import { createFileIconThemableTreeContainerScope } from '../../../../workbench/contrib/files/browser/views/explorerView.js'; import { IActivityService, NumberBadge } from '../../../../workbench/services/activity/common/activity.js'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../../workbench/services/editor/common/editorService.js'; +import { IEditorService, MODAL_GROUP, SIDE_GROUP } from '../../../../workbench/services/editor/common/editorService.js'; import { IExtensionService } from '../../../../workbench/services/extensions/common/extensions.js'; import { IWorkbenchLayoutService } from '../../../../workbench/services/layout/browser/layoutService.js'; import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; @@ -656,39 +656,53 @@ export class ChangesViewPane extends ViewPane { if (this.tree) { const tree = this.tree; - this.renderDisposables.add(tree.onDidOpen(async (e) => { - if (!e.element) { - return; - } - - // Ignore folder elements - only open files - if (!isChangesFileItem(e.element)) { - return; - } + const openFileItem = (item: IChangesFileItem, items: IChangesFileItem[], sideBySide: boolean) => { + const { uri: modifiedFileUri, originalUri, isDeletion } = item; + const currentIndex = items.indexOf(item); + + const navigation = { + total: items.length, + current: currentIndex, + navigate: (index: number) => { + const target = items[index]; + if (target) { + openFileItem(target, items, false); + } + } + }; - const { uri: modifiedFileUri, originalUri, isDeletion } = e.element; + const group = sideBySide ? SIDE_GROUP : MODAL_GROUP; if (isDeletion && originalUri) { - await this.editorService.openEditor({ + this.editorService.openEditor({ resource: originalUri, - options: e.editorOptions - }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + options: { modal: { navigation } } + }, group); return; } if (originalUri) { - await this.editorService.openEditor({ + this.editorService.openEditor({ original: { resource: originalUri }, modified: { resource: modifiedFileUri }, - options: e.editorOptions - }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + options: { modal: { navigation } } + }, group); return; } - await this.editorService.openEditor({ + this.editorService.openEditor({ resource: modifiedFileUri, - options: e.editorOptions - }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + options: { modal: { navigation } } + }, group); + }; + + this.renderDisposables.add(tree.onDidOpen((e) => { + if (!e.element || !isChangesFileItem(e.element)) { + return; + } + + const items = combinedEntriesObs.get(); + openFileItem(e.element, items, e.sideBySide); })); } diff --git a/src/vs/sessions/contrib/chat/browser/branchPicker.ts b/src/vs/sessions/contrib/chat/browser/branchPicker.ts new file mode 100644 index 0000000000000..f00e7f42abd7c --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/branchPicker.ts @@ -0,0 +1,198 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from '../../../../base/browser/dom.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { localize } from '../../../../nls.js'; +import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js'; +import { ActionListItemKind, IActionListDelegate, IActionListItem } from '../../../../platform/actionWidget/browser/actionList.js'; +import { IGitRepository } from '../../../../workbench/contrib/git/common/gitService.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { INewSession } from './newSession.js'; + +const COPILOT_WORKTREE_PATTERN = 'copilot-worktree-'; +const FILTER_THRESHOLD = 10; + +interface IBranchItem { + readonly name: string; +} + +/** + * A self-contained widget for selecting a git branch. + * Uses `IGitRepository.getRefs` to list local branches. + * Copilot worktree branches are shown in a collapsible section; + * other branches are listed without a section header. + * Writes the selected branch to the new session object. + */ +export class BranchPicker extends Disposable { + + private _selectedBranch: string | undefined; + private _newSession: INewSession | undefined; + private _branches: string[] = []; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + private readonly _renderDisposables = this._register(new DisposableStore()); + private _slotElement: HTMLElement | undefined; + private _triggerElement: HTMLElement | undefined; + + get selectedBranch(): string | undefined { + return this._selectedBranch; + } + + constructor( + @IActionWidgetService private readonly actionWidgetService: IActionWidgetService, + ) { + super(); + } + + /** + * Sets the new session that this picker writes to. + */ + setNewSession(session: INewSession | undefined): void { + this._newSession = session; + } + + /** + * Sets the git repository and loads its branches. + * When undefined, the picker is shown disabled. + */ + async setRepository(repository: IGitRepository | undefined): Promise { + this._branches = []; + this._selectedBranch = undefined; + + if (!repository) { + this._newSession?.setBranch(undefined); + this._updateTriggerLabel(); + return; + } + + const refs = await repository.getRefs({ pattern: 'refs/heads' }); + this._branches = refs + .map(ref => ref.name) + .filter((name): name is string => !!name) + .filter(name => !name.includes(COPILOT_WORKTREE_PATTERN)); + + // Select active branch, main, master, or the first branch by default + const defaultBranch = this._branches.find(b => b === repository.state.get().HEAD?.name) + ?? this._branches.find(b => b === 'main') + ?? this._branches.find(b => b === 'master') + ?? this._branches[0]; + if (defaultBranch) { + this._selectBranch(defaultBranch); + } + + this._updateTriggerLabel(); + } + + /** + * Renders the branch picker trigger into the given container. + */ + render(container: HTMLElement): void { + this._renderDisposables.clear(); + + const slot = dom.append(container, dom.$('.sessions-chat-picker-slot')); + this._slotElement = slot; + this._renderDisposables.add({ dispose: () => slot.remove() }); + + const trigger = dom.append(slot, dom.$('a.action-label')); + trigger.tabIndex = 0; + trigger.role = 'button'; + this._triggerElement = trigger; + this._updateTriggerLabel(); + + this._renderDisposables.add(dom.addDisposableListener(trigger, dom.EventType.CLICK, (e) => { + dom.EventHelper.stop(e, true); + this.showPicker(); + })); + + this._renderDisposables.add(dom.addDisposableListener(trigger, dom.EventType.KEY_DOWN, (e) => { + if (e.key === 'Enter' || e.key === ' ') { + dom.EventHelper.stop(e, true); + this.showPicker(); + } + })); + } + + /** + * Shows or hides the picker. + */ + setVisible(visible: boolean): void { + if (this._slotElement) { + this._slotElement.style.display = visible ? '' : 'none'; + } + } + + /** + * Shows the branch picker dropdown anchored to the trigger element. + */ + showPicker(): void { + if (!this._triggerElement || this.actionWidgetService.isVisible || this._branches.length === 0) { + return; + } + + const items = this._buildItems(); + const triggerElement = this._triggerElement; + const delegate: IActionListDelegate = { + onSelect: (item) => { + this.actionWidgetService.hide(); + this._selectBranch(item.name); + }, + onHide: () => { triggerElement.focus(); }, + }; + + const totalActions = items.filter(i => i.kind === ActionListItemKind.Action).length; + + this.actionWidgetService.show( + 'branchPicker', + false, + items, + delegate, + this._triggerElement, + undefined, + [], + { + getAriaLabel: (item) => item.label ?? '', + getWidgetAriaLabel: () => localize('branchPicker.ariaLabel', "Branch Picker"), + }, + totalActions > FILTER_THRESHOLD ? { showFilter: true, filterPlaceholder: localize('branchPicker.filter', "Filter branches...") } : undefined, + ); + } + + private _buildItems(): IActionListItem[] { + return this._branches.map(branch => ({ + kind: ActionListItemKind.Action, + label: branch, + group: { title: '', icon: this._selectedBranch === branch ? Codicon.check : Codicon.blank }, + item: { name: branch }, + })); + } + + private _selectBranch(branch: string): void { + if (this._selectedBranch !== branch) { + this._selectedBranch = branch; + this._newSession?.setBranch(branch); + this._onDidChange.fire(branch); + this._updateTriggerLabel(); + } + } + + private _updateTriggerLabel(): void { + if (!this._triggerElement) { + return; + } + dom.clearNode(this._triggerElement); + const isDisabled = this._branches.length === 0; + const label = this._selectedBranch ?? localize('branchPicker.select', "Branch"); + dom.append(this._triggerElement, renderIcon(Codicon.gitBranch)); + const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label')); + labelSpan.textContent = label; + dom.append(this._triggerElement, renderIcon(Codicon.chevronDown)); + this._slotElement?.classList.toggle('disabled', isDisabled); + } +} diff --git a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts index 8fa8db33b1c52..d337a84b2db72 100644 --- a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts @@ -36,6 +36,7 @@ import { NewChatViewPane, SessionsViewId } from './newChatViewPane.js'; import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/viewPaneContainer.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { ChatViewPane } from '../../../../workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.js'; +import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js'; export class OpenSessionWorktreeInVSCodeAction extends Action2 { static readonly ID = 'chat.openSessionWorktreeInVSCode'; @@ -156,6 +157,7 @@ MenuRegistry.appendMenuItem(Menus.TitleBarRight, { icon: Codicon.folderOpened, group: 'navigation', order: 9, + when: IsAuxiliaryWindowContext.toNegated() }); diff --git a/src/vs/sessions/contrib/chat/browser/folderPicker.ts b/src/vs/sessions/contrib/chat/browser/folderPicker.ts index 034ac24e4677a..dddc95aed2bc1 100644 --- a/src/vs/sessions/contrib/chat/browser/folderPicker.ts +++ b/src/vs/sessions/contrib/chat/browser/folderPicker.ts @@ -17,6 +17,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { IWorkspacesService, isRecentFolder } from '../../../../platform/workspaces/common/workspaces.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { INewSession } from './newSession.js'; const STORAGE_KEY_LAST_FOLDER = 'agentSessions.lastPickedFolder'; const STORAGE_KEY_RECENT_FOLDERS = 'agentSessions.recentlyPickedFolders'; @@ -42,6 +43,7 @@ export class FolderPicker extends Disposable { private _selectedFolderUri: URI | undefined; private _recentlyPickedFolders: URI[] = []; private _cachedRecentFolders: { uri: URI; label?: string }[] = []; + private _newSession: INewSession | undefined; private _triggerElement: HTMLElement | undefined; private readonly _renderDisposables = this._register(new DisposableStore()); @@ -50,6 +52,14 @@ export class FolderPicker extends Disposable { return this._selectedFolderUri; } + /** + * Sets the pending session that this picker writes to. + * When the user selects a folder, it calls `setRepoUri` on the session. + */ + setNewSession(session: INewSession | undefined): void { + this._newSession = session; + } + constructor( @IActionWidgetService private readonly actionWidgetService: IActionWidgetService, @IStorageService private readonly storageService: IStorageService, @@ -175,6 +185,7 @@ export class FolderPicker extends Disposable { this._addToRecentlyPickedFolders(folderUri); this.storageService.store(STORAGE_KEY_LAST_FOLDER, folderUri.toString(), StorageScope.PROFILE, StorageTarget.MACHINE); this._updateTriggerLabel(this._triggerElement); + this._newSession?.setRepoUri(folderUri); this._onDidSelectFolder.fire(folderUri); } diff --git a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css index 75d90b5f97d74..a6987292c9b5e 100644 --- a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css +++ b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css @@ -298,6 +298,17 @@ color: var(--vscode-foreground); } +.sessions-chat-picker-slot.disabled .action-label { + opacity: 0.5; + cursor: default; + pointer-events: none; +} + +.sessions-chat-picker-slot.disabled .action-label:hover { + background-color: transparent; + color: var(--vscode-descriptionForeground); +} + .sessions-chat-picker-slot .action-label .codicon { font-size: 14px; flex-shrink: 0; diff --git a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts index 10dc60a3800a1..431e7e953d200 100644 --- a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts +++ b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts @@ -7,14 +7,11 @@ import './media/chatWidget.css'; import './media/chatWelcomePart.css'; import * as dom from '../../../../base/browser/dom.js'; import { Codicon } from '../../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; import { toAction } from '../../../../base/common/actions.js'; -import { Radio } from '../../../../base/browser/ui/radio/radio.js'; -import { DropdownMenuActionViewItem } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; +import { Emitter } from '../../../../base/common/event.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { IObservable, observableValue } from '../../../../base/common/observable.js'; +import { observableValue } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; @@ -32,149 +29,39 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; -import { isEqual } from '../../../../base/common/resources.js'; import { localize } from '../../../../nls.js'; import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; import { ChatSessionPosition, getResourceForNewChatSession } from '../../../../workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.js'; import { ChatSessionPickerActionItem, IChatSessionPickerDelegate } from '../../../../workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.js'; import { SearchableOptionPickerActionItem } from '../../../../workbench/contrib/chat/browser/chatSessions/searchableOptionPickerActionItem.js'; -import { ChatAgentLocation, ChatModeKind } from '../../../../workbench/contrib/chat/common/constants.js'; -import { IChatSendRequestOptions } from '../../../../workbench/contrib/chat/common/chatService/chatService.js'; import { IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../../../../workbench/contrib/chat/common/languageModels.js'; import { IModelPickerDelegate } from '../../../../workbench/contrib/chat/browser/widget/input/modelPickerActionItem.js'; import { EnhancedModelPickerActionItem } from '../../../../workbench/contrib/chat/browser/widget/input/modelPickerActionItem2.js'; import { IChatInputPickerOptions } from '../../../../workbench/contrib/chat/browser/widget/input/chatInputPickerActionItem.js'; -import { WorkspaceFolderCountContext } from '../../../../workbench/common/contextkeys.js'; import { IViewDescriptorService } from '../../../../workbench/common/views.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { IViewPaneOptions, ViewPane } from '../../../../workbench/browser/parts/views/viewPane.js'; import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js'; import { getSimpleEditorOptions } from '../../../../workbench/contrib/codeEditor/browser/simpleEditorOptions.js'; -import { IChatRequestVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js'; import { isString } from '../../../../base/common/types.js'; import { NewChatContextAttachments } from './newChatContextAttachments.js'; import { GITHUB_REMOTE_FILE_SCHEME } from '../../fileTreeView/browser/githubFileSystemProvider.js'; import { FolderPicker } from './folderPicker.js'; - -// #region --- Target Config --- - -/** - * A dropdown menu action item that shows an icon, a text label, and a chevron. - */ -class LabeledDropdownMenuActionViewItem extends DropdownMenuActionViewItem { - protected override renderLabel(element: HTMLElement): null { - // Render icon as a separate codicon element - const classNames = typeof this.options.classNames === 'string' - ? this.options.classNames.split(/\s+/g).filter(s => !!s) - : (this.options.classNames ?? []); - if (classNames.length > 0) { - const icon = dom.append(element, dom.$('span')); - icon.classList.add('codicon', ...classNames); - } - - // Add text label (not affected by codicon font) - const label = dom.append(element, dom.$('span.sessions-chat-dropdown-label')); - label.textContent = this._action.label; - - // Add chevron - dom.append(element, renderIcon(Codicon.chevronDown)); - - return null; - } -} - -/** - * Tracks which agent session targets are available and which is selected. - * Targets are fixed at construction time; only the selection changes. - */ -export interface ITargetConfig { - readonly allowedTargets: IObservable>; - readonly selectedTarget: IObservable; - readonly onDidChangeSelectedTarget: Event; - readonly onDidChangeAllowedTargets: Event>; - setSelectedTarget(target: AgentSessionProviders): void; -} - -export interface ITargetConfigOptions { - allowedTargets: AgentSessionProviders[]; - defaultTarget?: AgentSessionProviders; -} - -class TargetConfig extends Disposable implements ITargetConfig { - - private readonly _allowedTargets = observableValue>('allowedTargets', new Set()); - readonly allowedTargets: IObservable> = this._allowedTargets; - - private readonly _selectedTarget = observableValue('selectedTarget', undefined); - readonly selectedTarget: IObservable = this._selectedTarget; - - private readonly _onDidChangeSelectedTarget = this._register(new Emitter()); - readonly onDidChangeSelectedTarget = this._onDidChangeSelectedTarget.event; - - private readonly _onDidChangeAllowedTargets = this._register(new Emitter>()); - readonly onDidChangeAllowedTargets = this._onDidChangeAllowedTargets.event; - - constructor(options: ITargetConfigOptions) { - super(); - const initialSet = new Set(options.allowedTargets); - this._allowedTargets.set(initialSet, undefined); - const defaultTarget = options.defaultTarget && initialSet.has(options.defaultTarget) - ? options.defaultTarget - : initialSet.values().next().value; - this._selectedTarget.set(defaultTarget, undefined); - } - - setSelectedTarget(target: AgentSessionProviders): void { - const allowed = this._allowedTargets.get(); - if (!allowed.has(target)) { - throw new Error(`Target "${target}" is not in the allowed set`); - } - if (this._selectedTarget.get() !== target) { - this._selectedTarget.set(target, undefined); - this._onDidChangeSelectedTarget.fire(target); - } - } - - setAllowedTargets(targets: AgentSessionProviders[]): void { - const newSet = new Set(targets); - this._allowedTargets.set(newSet, undefined); - this._onDidChangeAllowedTargets.fire(newSet); - - // If the currently selected target is no longer allowed, switch to the first allowed target - const current = this._selectedTarget.get(); - if (current && !newSet.has(current)) { - const fallback = newSet.values().next().value; - this._selectedTarget.set(fallback, undefined); - this._onDidChangeSelectedTarget.fire(fallback); - } - } -} - -// #endregion +import { IGitService } from '../../../../workbench/contrib/git/common/gitService.js'; +import { IsolationModePicker, SessionTargetPicker } from './sessionTargetPicker.js'; +import { BranchPicker } from './branchPicker.js'; +import { INewSession } from './newSession.js'; // #region --- Chat Welcome Widget --- -/** - * Data passed to the `onSendRequest` callback when the user submits a query. - */ -export interface INewChatSendRequestData { - readonly resource: URI; - readonly target: AgentSessionProviders; - readonly query: string; - readonly sendOptions: IChatSendRequestOptions; - readonly selectedOptions: ReadonlyMap; - readonly folderUri?: URI; - readonly attachedContext?: IChatRequestVariableEntry[]; -} - /** * Options for creating a `NewChatWidget`. */ -export interface INewChatWidgetOptions { - readonly targetConfig: ITargetConfigOptions; - readonly onSendRequest?: (data: INewChatSendRequestData) => void; +interface INewChatWidgetOptions { + readonly allowedTargets: AgentSessionProviders[]; + readonly defaultTarget: AgentSessionProviders; readonly sessionPosition?: ChatSessionPosition; } @@ -187,26 +74,25 @@ export interface INewChatWidgetOptions { */ class NewChatWidget extends Disposable { - private readonly _targetConfig: TargetConfig; + private readonly _targetPicker: SessionTargetPicker; + private readonly _isolationModePicker: IsolationModePicker; + private readonly _branchPicker: BranchPicker; private readonly _options: INewChatWidgetOptions; // Input private _editor!: CodeEditorWidget; private readonly _currentLanguageModel = observableValue('currentLanguageModel', undefined); private readonly _modelPickerDisposable = this._register(new MutableDisposable()); - private _pendingSessionResource: URI | undefined; + + // Pending session + private readonly _newSession = this._register(new MutableDisposable()); + private readonly _newSessionListener = this._register(new MutableDisposable()); // Welcome part - private readonly _welcomeContentDisposables = this._register(new DisposableStore()); private _pickersContainer: HTMLElement | undefined; - private _targetDropdownContainer: HTMLElement | undefined; private _extensionPickersLeftContainer: HTMLElement | undefined; private _extensionPickersRightContainer: HTMLElement | undefined; private _inputSlot: HTMLElement | undefined; - private _localModeContainer: HTMLElement | undefined; - private _localModeDropdownContainer: HTMLElement | undefined; - private _localModePickersContainer: HTMLElement | undefined; - private _localMode: 'workspace' | 'worktree' = 'worktree'; private readonly _folderPicker: FolderPicker; private _folderPickerContainer: HTMLElement | undefined; private readonly _pickerWidgets = new Map(); @@ -227,56 +113,30 @@ class NewChatWidget extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IContextMenuService contextMenuService: IContextMenuService, @ILogService private readonly logService: ILogService, @IHoverService _hoverService: IHoverService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @ISessionsManagementService private readonly sessionsManagementService: ISessionsManagementService, + @IGitService private readonly gitService: IGitService, ) { super(); this._contextAttachments = this._register(this.instantiationService.createInstance(NewChatContextAttachments)); this._folderPicker = this._register(this.instantiationService.createInstance(FolderPicker)); - this._targetConfig = this._register(new TargetConfig(options.targetConfig)); + this._targetPicker = this._register(new SessionTargetPicker(options.allowedTargets, options.defaultTarget)); + this._isolationModePicker = this._register(this.instantiationService.createInstance(IsolationModePicker)); + this._branchPicker = this._register(this.instantiationService.createInstance(BranchPicker)); this._options = options; - // When folder changes, notify extension and re-render pickers - this._register(this._folderPicker.onDidSelectFolder(() => { - this._notifyFolderSelection(); - this._renderExtensionPickers(true); - })); - - // When target changes, regenerate pending resource - this._register(this._targetConfig.onDidChangeSelectedTarget(() => { - this._generatePendingSessionResource(); - this._notifyFolderSelection(); - this._renderExtensionPickers(true); - this._renderLocalModePicker(); - })); - - this._register(this._targetConfig.onDidChangeAllowedTargets(() => { - if (this._targetDropdownContainer) { - dom.clearNode(this._targetDropdownContainer); - this._renderTargetDropdown(this._targetDropdownContainer); - } - this._renderExtensionPickers(true); - })); - - // Listen for option group changes to re-render pickers - this._register(this.chatSessionsService.onDidChangeOptionGroups(() => this._renderExtensionPickers())); - - // React to chat session option changes - this._register(this.chatSessionsService.onDidChangeSessionOptions((e: URI | undefined) => { - if (this._pendingSessionResource && isEqual(this._pendingSessionResource, e)) { - this._syncOptionsFromSession(this._pendingSessionResource); - this._renderExtensionPickers(); - } + // When target changes, create new session + this._register(this._targetPicker.onDidChangeTarget((target) => { + this._createNewSession(); + const isLocal = target === AgentSessionProviders.Background; + this._isolationModePicker.setVisible(isLocal); + this._branchPicker.setVisible(isLocal); })); - const workspaceFolderCountKey = new Set([WorkspaceFolderCountContext.key]); this._register(this.contextKeyService.onDidChangeContext(e => { - if (e.affectsSome(workspaceFolderCountKey)) { - this._renderExtensionPickers(true); - } if (this._whenClauseKeys.size > 0 && e.affectsSome(this._whenClauseKeys)) { this._renderExtensionPickers(true); } @@ -314,11 +174,17 @@ class NewChatWidget extends Disposable { this._createBottomToolbar(inputArea); this._inputSlot.appendChild(inputArea); - // Local mode picker (below the input, shown when Local is selected) - this._localModeContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-local-mode')); - this._localModeDropdownContainer = dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-left')); - dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-spacer')); - this._localModePickersContainer = dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-right')); + // Isolation mode and branch pickers (below the input, shown when Local target is selected) + const isolationContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-local-mode')); + this._isolationModePicker.render(isolationContainer); + dom.append(isolationContainer, dom.$('.sessions-chat-local-mode-spacer')); + const branchContainer = dom.append(isolationContainer, dom.$('.sessions-chat-local-mode-right')); + this._branchPicker.render(branchContainer); + + // Set initial visibility based on default target + const isLocal = this._targetPicker.selectedTarget === AgentSessionProviders.Background; + this._isolationModePicker.setVisible(isLocal); + this._branchPicker.setVisible(isLocal); // Render target buttons & extension pickers this._renderOptionGroupPickers(); @@ -326,49 +192,75 @@ class NewChatWidget extends Disposable { // Initialize model picker this._initDefaultModel(); - // Generate pending resource for option changes - this._generatePendingSessionResource(); - - // Render local mode picker - this._renderLocalModePicker(); + // Create initial session + this._createNewSession(); // Reveal welcomeElement.classList.add('revealed'); } - private _getEffectiveTarget(): AgentSessionProviders | undefined { - const target = this._targetConfig.selectedTarget.get(); - if (target === AgentSessionProviders.Local && this._localMode === 'worktree') { - return AgentSessionProviders.Background; + private async _createNewSession(): Promise { + const target = this._targetPicker.selectedTarget; + const defaultRepoUri = this._folderPicker.selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; + const resource = getResourceForNewChatSession({ + type: target, + position: this._options.sessionPosition ?? ChatSessionPosition.Sidebar, + displayName: '', + }); + + try { + const session = await this.sessionsManagementService.createNewSessionForTarget(target, resource, defaultRepoUri); + this._setNewSession(session); + } catch (e) { + this.logService.error('Failed to create new session:', e); } - return target; } - private readonly _pendingSessionResources = new Map(); + private _setNewSession(session: INewSession): void { + this._newSession.value = session; - private _generatePendingSessionResource(): void { - const target = this._getEffectiveTarget(); - if (!target || target === AgentSessionProviders.Local) { - this._pendingSessionResource = undefined; - return; + // Wire pickers to the new session + this._folderPicker.setNewSession(session); + this._isolationModePicker.setNewSession(session); + this._branchPicker.setNewSession(session); + + // Set the current model on the session + const currentModel = this._currentLanguageModel.get(); + if (currentModel) { + session.setModelId(currentModel.identifier); } - // Reuse existing pending resource for the same target type - const existing = this._pendingSessionResources.get(target); - if (existing) { - this._pendingSessionResource = existing; - return; + // Open repository for the session's repoUri + if (session.repoUri) { + this._openRepository(session.repoUri); } - this._pendingSessionResource = getResourceForNewChatSession({ - type: target, - position: this._options.sessionPosition ?? ChatSessionPosition.Sidebar, - displayName: '', + // Render extension pickers for the new session + this._renderExtensionPickers(true); + + // Listen for session changes + this._newSessionListener.value = session.onDidChange((changeType) => { + if (changeType === 'repoUri' && session.repoUri) { + this._openRepository(session.repoUri); + } + if (changeType === 'isolationMode') { + this._branchPicker.setVisible(session.isolationMode === 'worktree'); + } + if (changeType === 'options') { + this._syncOptionsFromSession(session.resource); + this._renderExtensionPickers(); + } }); - this._pendingSessionResources.set(target, this._pendingSessionResource); + } - this.sessionsManagementService.createNewPendingSession(this._pendingSessionResource,) - .catch((err) => this.logService.trace('Failed to create pending session:', err)); + private _openRepository(folderUri: URI): void { + this.gitService.openRepository(folderUri).then(repository => { + this._isolationModePicker.setRepository(repository); + this._branchPicker.setRepository(repository); + }).catch(() => { + this._isolationModePicker.setRepository(undefined); + this._branchPicker.setRepository(undefined); + }); } // --- Editor --- @@ -435,9 +327,9 @@ class NewChatWidget extends Disposable { * Local targets use the workspace folder; cloud targets construct a github-remote-file:// URI. */ private _getContextFolderUri(): URI | undefined { - const target = this._getEffectiveTarget(); + const target = this._targetPicker.selectedTarget; - if (!target || target === AgentSessionProviders.Local || target === AgentSessionProviders.Background) { + if (target === AgentSessionProviders.Background) { return this._folderPicker.selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; } @@ -481,10 +373,11 @@ class NewChatWidget extends Disposable { currentModel: this._currentLanguageModel, setModel: (model: ILanguageModelChatMetadataAndIdentifier) => { this._currentLanguageModel.set(model, undefined); + this._newSession.value?.setModelId(model.identifier); }, getModels: () => this._getAvailableModels(), canManageModels: () => true, - showCuratedModels: () => this._localMode === 'workspace', + showCuratedModels: () => false, }; const pickerOptions: IChatInputPickerOptions = { @@ -530,9 +423,6 @@ class NewChatWidget extends Disposable { if (!model.metadata.isUserSelectable) { return false; } - if (model.metadata.targetChatSessionType === AgentSessionProviders.Background) { - return false; - } return true; } @@ -543,15 +433,15 @@ class NewChatWidget extends Disposable { return; } - this._disposePickerWidgets(); + this._clearExtensionPickers(); dom.clearNode(this._pickersContainer); const pickersRow = dom.append(this._pickersContainer, dom.$('.chat-full-welcome-pickers')); // Left half: target switcher (right-justified within its half) const leftHalf = dom.append(pickersRow, dom.$('.sessions-chat-pickers-left-half')); - this._targetDropdownContainer = dom.append(leftHalf, dom.$('.sessions-chat-dropdown-wrapper')); - this._renderTargetDropdown(this._targetDropdownContainer); + const targetDropdownContainer = dom.append(leftHalf, dom.$('.sessions-chat-dropdown-wrapper')); + this._targetPicker.render(targetDropdownContainer); // Right half: separator + pickers (left-justified within its half) const rightHalf = dom.append(pickersRow, dom.$('.sessions-chat-pickers-right-half')); @@ -565,140 +455,17 @@ class NewChatWidget extends Disposable { this._renderExtensionPickers(); } - private _renderTargetDropdown(container: HTMLElement): void { - const allowed = this._targetConfig.allowedTargets.get(); - if (allowed.size === 0) { - return; - } - - const activeType = this._targetConfig.selectedTarget.get() ?? AgentSessionProviders.Local; - const targets = [AgentSessionProviders.Local, AgentSessionProviders.Cloud].filter(t => allowed.has(t)); - const activeIndex = targets.indexOf(activeType); - - const radio = new Radio({ - items: targets.map(target => ({ - text: getAgentSessionProviderName(target), - isActive: target === activeType, - })), - }); - this._welcomeContentDisposables.add(radio); - container.appendChild(radio.domNode); - - if (activeIndex >= 0) { - radio.setActiveItem(activeIndex); - } - - this._welcomeContentDisposables.add(radio.onDidSelect(index => { - this._targetConfig.setSelectedTarget(targets[index]); - })); - } - - // --- Local mode picker (Workspace / Worktree) --- - - private readonly _localModeDisposables = this._register(new DisposableStore()); - - private _renderLocalModePicker(): void { - if (!this._localModeContainer || !this._localModeDropdownContainer || !this._localModePickersContainer) { - return; - } - - this._localModeDisposables.clear(); - dom.clearNode(this._localModeDropdownContainer); - dom.clearNode(this._localModePickersContainer); - - const selectedTarget = this._targetConfig.selectedTarget.get(); - if (selectedTarget !== AgentSessionProviders.Local) { - this._localModeContainer.style.visibility = 'hidden'; - return; - } - - this._localModeContainer.style.visibility = ''; - - // Dropdown button for Workspace / Worktree - const modeLabel = this._localMode === 'workspace' - ? localize('localMode.workspace', "Workspace") - : localize('localMode.worktree', "Worktree"); - const modeIcon = this._localMode === 'workspace' ? Codicon.folder : Codicon.worktree; - - const modeAction = toAction({ id: 'localMode', label: modeLabel, run: () => { } }); - const modeDropdown = this._localModeDisposables.add(new LabeledDropdownMenuActionViewItem( - modeAction, - { - getActions: () => [ - toAction({ - id: 'localMode.workspace', - label: localize('localMode.workspace', "Workspace"), - checked: this._localMode === 'workspace', - run: () => this._setLocalMode('workspace'), - }), - toAction({ - id: 'localMode.worktree', - label: localize('localMode.worktree', "Worktree"), - checked: this._localMode === 'worktree', - run: () => this._setLocalMode('worktree'), - }), - ], - }, - this.contextMenuService, - { classNames: [...ThemeIcon.asClassNameArray(modeIcon)] } - )); - const modeSlot = dom.append(this._localModeDropdownContainer, dom.$('.sessions-chat-picker-slot')); - modeDropdown.render(modeSlot); - - // Render pickers in the right side - this._renderLocalModePickers(); - } - - private _setLocalMode(mode: 'workspace' | 'worktree'): void { - if (this._localMode !== mode) { - this._localMode = mode; - this._generatePendingSessionResource(); - this._notifyFolderSelection(); - this._renderLocalModePicker(); - } - } - - private _notifyFolderSelection(): void { - this._selectedOptions.clear(); - if (!this._pendingSessionResource) { - return; - } - const folderUri = this._folderPicker.selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; - if (folderUri) { - this.chatSessionsService.notifySessionOptionsChange( - this._pendingSessionResource, - [{ optionId: 'repository', value: folderUri.fsPath }] - ).catch((err) => this.logService.error('Failed to notify extension of folder selection:', err)); - } - } - - private _renderLocalModePickers(): void { - if (!this._localModePickersContainer) { - return; - } - dom.clearNode(this._localModePickersContainer); - - if (this._localMode === 'worktree') { - // Worktree mode: render extension pickers for Background provider - this._renderExtensionPickersInContainer(this._localModePickersContainer, AgentSessionProviders.Background); - } - } - - // --- Welcome: Extension option pickers --- + // --- Welcome: Extension option pickers (Cloud target only) --- private _renderExtensionPickers(force?: boolean): void { if (!this._extensionPickersRightContainer) { return; } - const activeSessionType = this._getEffectiveTarget(); - if (!activeSessionType) { - this._clearExtensionPickers(); - return; - } + const activeSessionType = this._targetPicker.selectedTarget; - // For Local target, show folder picker in top row and handle bottom row - if (this._targetConfig.selectedTarget.get() === AgentSessionProviders.Local) { + // Extension pickers are only shown for Cloud target + if (activeSessionType === AgentSessionProviders.Background) { this._clearExtensionPickers(); if (this._folderPickerContainer) { this._folderPickerContainer.style.display = ''; @@ -706,7 +473,6 @@ class NewChatWidget extends Disposable { if (this._extensionPickersLeftContainer) { this._extensionPickersLeftContainer.style.display = 'block'; } - this._renderLocalModePicker(); return; } @@ -742,16 +508,6 @@ class NewChatWidget extends Disposable { return; } - visibleGroups.sort((a, b) => { - // Repo/folder pickers first, then others - const aRepo = isRepoOrFolderGroup(a) ? 0 : 1; - const bRepo = isRepoOrFolderGroup(b) ? 0 : 1; - if (aRepo !== bRepo) { - return aRepo - bRepo; - } - return (a.when ? 1 : 0) - (b.when ? 1 : 0); - }); - if (!force && this._pickerWidgets.size === visibleGroups.length) { const allMatch = visibleGroups.every(g => this._pickerWidgets.has(g.id)); if (allMatch) { @@ -761,7 +517,6 @@ class NewChatWidget extends Disposable { this._clearExtensionPickers(); - // Show the separator between target switcher and extension pickers if (this._extensionPickersLeftContainer) { this._extensionPickersLeftContainer.style.display = 'block'; } @@ -783,12 +538,7 @@ class NewChatWidget extends Disposable { this._updateOptionContextKey(optionGroup.id, option.id); emitter.fire(option); - if (this._pendingSessionResource) { - this.chatSessionsService.notifySessionOptionsChange( - this._pendingSessionResource, - [{ optionId: optionGroup.id, value: option }] - ).catch((err) => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); - } + this._newSession.value?.setOption(optionGroup.id, option); this._renderExtensionPickers(true); }, @@ -796,7 +546,7 @@ class NewChatWidget extends Disposable { const groups = this.chatSessionsService.getOptionGroupsForSessionType(activeSessionType); return groups?.find((g: { id: string }) => g.id === optionGroup.id); }, - getSessionResource: () => this._pendingSessionResource, + getSessionResource: () => this._newSession.value?.resource, }; const action = toAction({ id: optionGroup.id, label: optionGroup.name, run: () => { } }); @@ -808,77 +558,7 @@ class NewChatWidget extends Disposable { this._pickerWidgetDisposables.add(widget); this._pickerWidgets.set(optionGroup.id, widget); - // All pickers go to the right - const targetContainer = this._extensionPickersRightContainer!; - - const slot = dom.append(targetContainer, dom.$('.sessions-chat-picker-slot')); - widget.render(slot); - } - } - - private _renderExtensionPickersInContainer(container: HTMLElement, sessionType: AgentSessionProviders): void { - const optionGroups = this.chatSessionsService.getOptionGroupsForSessionType(sessionType); - if (!optionGroups || optionGroups.length === 0) { - return; - } - - const visibleGroups: IChatSessionProviderOptionGroup[] = []; - for (const group of optionGroups) { - if (isModelOptionGroup(group)) { - continue; - } - if (group.id === 'repository') { - continue; - } - const hasItems = group.items.length > 0 || (group.commands || []).length > 0 || !!group.searchable; - const passesWhenClause = this._evaluateOptionGroupVisibility(group); - if (hasItems && passesWhenClause) { - visibleGroups.push(group); - } - } - - for (const optionGroup of visibleGroups) { - const initialItem = this._getDefaultOptionForGroup(optionGroup); - const initialState = { group: optionGroup, item: initialItem }; - - if (initialItem) { - this._updateOptionContextKey(optionGroup.id, initialItem.id); - } - - const emitter = this._getOrCreateOptionEmitter(optionGroup.id); - const itemDelegate: IChatSessionPickerDelegate = { - getCurrentOption: () => this._selectedOptions.get(optionGroup.id) ?? this._getDefaultOptionForGroup(optionGroup), - onDidChangeOption: emitter.event, - setOption: (option: IChatSessionProviderOptionItem) => { - this._selectedOptions.set(optionGroup.id, option); - this._updateOptionContextKey(optionGroup.id, option.id); - emitter.fire(option); - - if (this._pendingSessionResource) { - this.chatSessionsService.notifySessionOptionsChange( - this._pendingSessionResource, - [{ optionId: optionGroup.id, value: option }] - ).catch((err) => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); - } - - this._renderLocalModePickers(); - }, - getOptionGroup: () => { - const groups = this.chatSessionsService.getOptionGroupsForSessionType(sessionType); - return groups?.find((g: { id: string }) => g.id === optionGroup.id); - }, - getSessionResource: () => this._pendingSessionResource, - }; - - const action = toAction({ id: optionGroup.id, label: optionGroup.name, run: () => { } }); - const widget = this.instantiationService.createInstance( - optionGroup.searchable ? SearchableOptionPickerActionItem : ChatSessionPickerActionItem, - action, initialState, itemDelegate - ); - - this._localModeDisposables.add(widget); - - const slot = dom.append(container, dom.$('.sessions-chat-picker-slot')); + const slot = dom.append(this._extensionPickersRightContainer!, dom.$('.sessions-chat-picker-slot')); widget.render(slot); } } @@ -897,8 +577,8 @@ class NewChatWidget extends Disposable { return selectedOption; } - if (this._pendingSessionResource) { - const sessionOption = this.chatSessionsService.getSessionOption(this._pendingSessionResource, optionGroup.id); + if (this._newSession.value) { + const sessionOption = this.chatSessionsService.getSessionOption(this._newSession.value.resource, optionGroup.id); if (!isString(sessionOption)) { return sessionOption; } @@ -908,7 +588,7 @@ class NewChatWidget extends Disposable { } private _syncOptionsFromSession(sessionResource: URI): void { - const activeSessionType = this._getEffectiveTarget(); + const activeSessionType = this._targetPicker.selectedTarget; if (!activeSessionType) { return; } @@ -959,12 +639,6 @@ class NewChatWidget extends Disposable { return emitter; } - private _disposePickerWidgets(): void { - this._pickerWidgetDisposables.clear(); - this._pickerWidgets.clear(); - this._optionEmitters.clear(); - } - private _clearExtensionPickers(): void { this._pickerWidgetDisposables.clear(); this._pickerWidgets.clear(); @@ -984,50 +658,23 @@ class NewChatWidget extends Disposable { private _send(): void { const query = this._editor.getModel()?.getValue().trim(); - if (!query) { - return; - } - - const target = this._getEffectiveTarget(); - if (!target) { - this.logService.warn('ChatWelcomeWidget: No target selected, cannot create session'); + const session = this._newSession.value; + if (!query || !session) { return; } - const position = this._options.sessionPosition ?? ChatSessionPosition.Sidebar; - const resource = this._pendingSessionResource - ?? getResourceForNewChatSession({ type: target, position, displayName: '' }); - - const contribution = target !== AgentSessionProviders.Local - ? this.chatSessionsService.getChatSessionContribution(target) - : undefined; - - const sendOptions: IChatSendRequestOptions = { - location: ChatAgentLocation.Chat, - userSelectedModelId: this._currentLanguageModel.get()?.identifier, - modeInfo: { - kind: ChatModeKind.Agent, - isBuiltin: true, - modeInstructions: undefined, - modeId: 'agent', - applyCodeBlockSuggestionId: undefined, - }, - agentIdSilent: contribution?.type, - attachedContext: this._contextAttachments.attachments.length > 0 ? [...this._contextAttachments.attachments] : undefined, - }; - - const folderUri = this._folderPicker.selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; + session.setQuery(query); + session.setAttachedContext( + this._contextAttachments.attachments.length > 0 ? [...this._contextAttachments.attachments] : undefined + ); - this._options.onSendRequest?.({ - resource, - target, - query, - sendOptions, - selectedOptions: new Map(this._selectedOptions), - folderUri, - attachedContext: this._contextAttachments.attachments.length > 0 ? [...this._contextAttachments.attachments] : undefined, - }); + this.sessionsManagementService.sendRequestForNewSession( + session.resource + ).catch(e => this.logService.error('Failed to send request:', e)); + // Clear sent session so a fresh one is created next time + this._newSession.clear(); + this._newSessionListener.clear(); this._contextAttachments.clear(); } @@ -1037,16 +684,12 @@ class NewChatWidget extends Disposable { this._editor?.layout(); } - setVisible(_visible: boolean): void { - // no-op - } - focusInput(): void { this._editor?.focus(); } updateAllowedTargets(targets: AgentSessionProviders[]): void { - this._targetConfig.setAllowedTargets(targets); + this._targetPicker.updateAllowedTargets(targets); } } @@ -1074,9 +717,7 @@ export class NewChatViewPane extends ViewPane { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, @IHoverService hoverService: IHoverService, - @ISessionsManagementService private readonly activeSessionService: ISessionsManagementService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @ILogService private readonly logService: ILogService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); } @@ -1087,15 +728,8 @@ export class NewChatViewPane extends ViewPane { this._widget = this._register(this.instantiationService.createInstance( NewChatWidget, { - targetConfig: { - allowedTargets: this.computeAllowedTargets(), - defaultTarget: AgentSessionProviders.Local, - }, - onSendRequest: (data) => { - this.activeSessionService.sendRequestForNewSession( - data.resource, data.query, data.sendOptions, data.selectedOptions, data.folderUri - ).catch(e => this.logService.error('NewChatViewPane: Failed to open session and send request', e)); - }, + allowedTargets: this.computeAllowedTargets(), + defaultTarget: AgentSessionProviders.Background, } satisfies INewChatWidgetOptions, )); @@ -1108,7 +742,7 @@ export class NewChatViewPane extends ViewPane { } private computeAllowedTargets(): AgentSessionProviders[] { - const targets: AgentSessionProviders[] = [AgentSessionProviders.Local, AgentSessionProviders.Cloud]; + const targets: AgentSessionProviders[] = [AgentSessionProviders.Background, AgentSessionProviders.Cloud]; return targets; } @@ -1124,7 +758,6 @@ export class NewChatViewPane extends ViewPane { override setVisible(visible: boolean): void { super.setVisible(visible); - this._widget?.setVisible(visible); if (visible) { this._widget?.focusInput(); } @@ -1157,20 +790,3 @@ function isRepoOrFolderGroup(group: IChatSessionProviderOptionGroup): boolean { nameLower === 'repository' || nameLower === 'repositories' || nameLower === 'folder' || nameLower === 'folders'; } - -function getAgentSessionProviderName(provider: AgentSessionProviders): string { - switch (provider) { - case AgentSessionProviders.Local: - return localize('chat.session.providerLabel.local', "Local"); - case AgentSessionProviders.Background: - return localize('chat.session.providerLabel.background', "Worktree"); - case AgentSessionProviders.Cloud: - return localize('chat.session.providerLabel.cloud', "Cloud"); - case AgentSessionProviders.Claude: - return 'Claude'; - case AgentSessionProviders.Codex: - return 'Codex'; - case AgentSessionProviders.Growth: - return 'Growth'; - } -} diff --git a/src/vs/sessions/contrib/chat/browser/newSession.ts b/src/vs/sessions/contrib/chat/browser/newSession.ts new file mode 100644 index 0000000000000..266a2c2e7f9f3 --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/newSession.ts @@ -0,0 +1,221 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; +import { IsolationMode } from './sessionTargetPicker.js'; +import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; +import { IActiveSessionItem } from '../../sessions/browser/sessionsManagementService.js'; + +import { IChatRequestVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js'; + +export type NewSessionChangeType = 'repoUri' | 'isolationMode' | 'branch' | 'options'; + +/** + * A new session represents a session being configured before the first + * request is sent. It holds the user's selections (repoUri, isolationMode) + * and fires a single event when any property changes. + */ +export interface INewSession extends IDisposable { + readonly resource: URI; + readonly target: AgentSessionProviders; + readonly activeSessionItem: IActiveSessionItem; + readonly repoUri: URI | undefined; + readonly isolationMode: IsolationMode; + readonly branch: string | undefined; + readonly modelId: string | undefined; + readonly query: string | undefined; + readonly attachedContext: IChatRequestVariableEntry[] | undefined; + readonly selectedOptions: ReadonlyMap; + readonly onDidChange: Event; + setRepoUri(uri: URI): void; + setIsolationMode(mode: IsolationMode): void; + setBranch(branch: string | undefined): void; + setModelId(modelId: string | undefined): void; + setQuery(query: string): void; + setAttachedContext(context: IChatRequestVariableEntry[] | undefined): void; + setOption(optionId: string, value: IChatSessionProviderOptionItem | string): void; +} + +const REPOSITORY_OPTION_ID = 'repository'; +const BRANCH_OPTION_ID = 'branch'; +const ISOLATION_OPTION_ID = 'isolation'; + +/** + * Local new session for Background agent sessions. + * Fires `onDidChange` for both `repoUri` and `isolationMode` changes. + * Notifies the extension service with session options for each property change. + */ +export class LocalNewSession extends Disposable implements INewSession { + + private _repoUri: URI | undefined; + private _isolationMode: IsolationMode = 'worktree'; + private _branch: string | undefined; + private _modelId: string | undefined; + private _query: string | undefined; + private _attachedContext: IChatRequestVariableEntry[] | undefined; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + readonly target = AgentSessionProviders.Background; + readonly selectedOptions = new Map(); + + get resource(): URI { return this.activeSessionItem.resource; } + get repoUri(): URI | undefined { return this._repoUri; } + get isolationMode(): IsolationMode { return this._isolationMode; } + get branch(): string | undefined { return this._branch; } + get modelId(): string | undefined { return this._modelId; } + get query(): string | undefined { return this._query; } + get attachedContext(): IChatRequestVariableEntry[] | undefined { return this._attachedContext; } + + constructor( + readonly activeSessionItem: IActiveSessionItem, + defaultRepoUri: URI | undefined, + private readonly chatSessionsService: IChatSessionsService, + private readonly logService: ILogService, + ) { + super(); + if (defaultRepoUri) { + this._repoUri = defaultRepoUri; + this.setOption(REPOSITORY_OPTION_ID, defaultRepoUri.fsPath); + } + } + + setRepoUri(uri: URI): void { + this._repoUri = uri; + this._isolationMode = 'workspace'; + this._branch = undefined; + this._onDidChange.fire('repoUri'); + this.setOption(REPOSITORY_OPTION_ID, uri.fsPath); + } + + setIsolationMode(mode: IsolationMode): void { + if (this._isolationMode !== mode) { + this._isolationMode = mode; + this._onDidChange.fire('isolationMode'); + this.setOption(ISOLATION_OPTION_ID, mode); + } + } + + setBranch(branch: string | undefined): void { + if (this._branch !== branch) { + this._branch = branch; + this._onDidChange.fire('branch'); + this.setOption(BRANCH_OPTION_ID, branch ?? ''); + } + } + + setModelId(modelId: string | undefined): void { + this._modelId = modelId; + } + + setQuery(query: string): void { + this._query = query; + } + + setAttachedContext(context: IChatRequestVariableEntry[] | undefined): void { + this._attachedContext = context; + } + + setOption(optionId: string, value: IChatSessionProviderOptionItem | string): void { + if (typeof value === 'string') { + this.selectedOptions.set(optionId, { id: value, name: value }); + } else { + this.selectedOptions.set(optionId, value); + } + this.chatSessionsService.notifySessionOptionsChange( + this.resource, + [{ optionId, value }] + ).catch((err) => this.logService.error(`Failed to notify session option ${optionId} change:`, err)); + } +} + +/** + * Remote new session for Cloud agent sessions. + * Fires `onDidChange` and notifies the extension service when `repoUri` changes. + * Ignores `isolationMode` (not relevant for cloud). + */ +export class RemoteNewSession extends Disposable implements INewSession { + + private _repoUri: URI | undefined; + private _isolationMode: IsolationMode = 'worktree'; + private _modelId: string | undefined; + private _query: string | undefined; + private _attachedContext: IChatRequestVariableEntry[] | undefined; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + readonly selectedOptions = new Map(); + + get resource(): URI { return this.activeSessionItem.resource; } + get repoUri(): URI | undefined { return this._repoUri; } + get isolationMode(): IsolationMode { return this._isolationMode; } + get branch(): string | undefined { return undefined; } + get modelId(): string | undefined { return this._modelId; } + get query(): string | undefined { return this._query; } + get attachedContext(): IChatRequestVariableEntry[] | undefined { return this._attachedContext; } + + constructor( + readonly activeSessionItem: IActiveSessionItem, + readonly target: AgentSessionProviders, + private readonly chatSessionsService: IChatSessionsService, + private readonly logService: ILogService, + ) { + super(); + + // Listen for extension-driven option group and session option changes + this._register(this.chatSessionsService.onDidChangeOptionGroups(() => { + this._onDidChange.fire('options'); + })); + this._register(this.chatSessionsService.onDidChangeSessionOptions((e: URI | undefined) => { + if (isEqual(this.resource, e)) { + this._onDidChange.fire('options'); + } + })); + } + + setRepoUri(uri: URI): void { + this._repoUri = uri; + this._onDidChange.fire('repoUri'); + this.setOption('repository', uri.fsPath); + } + + setIsolationMode(_mode: IsolationMode): void { + // No-op for remote sessions — isolation mode is not relevant + } + + setBranch(_branch: string | undefined): void { + // No-op for remote sessions — branch is not relevant + } + + setModelId(modelId: string | undefined): void { + this._modelId = modelId; + } + + setQuery(query: string): void { + this._query = query; + } + + setAttachedContext(context: IChatRequestVariableEntry[] | undefined): void { + this._attachedContext = context; + } + + setOption(optionId: string, value: IChatSessionProviderOptionItem | string): void { + if (typeof value !== 'string') { + this.selectedOptions.set(optionId, value); + } + this._onDidChange.fire('options'); + this.chatSessionsService.notifySessionOptionsChange( + this.resource, + [{ optionId, value }] + ).catch((err) => this.logService.error(`Failed to notify extension of ${optionId} change:`, err)); + } +} diff --git a/src/vs/sessions/contrib/chat/browser/runScriptAction.ts b/src/vs/sessions/contrib/chat/browser/runScriptAction.ts index dfa11fd710d40..d196c18f3039b 100644 --- a/src/vs/sessions/contrib/chat/browser/runScriptAction.ts +++ b/src/vs/sessions/contrib/chat/browser/runScriptAction.ts @@ -17,6 +17,7 @@ import { ITerminalService } from '../../../../workbench/contrib/terminal/browser import { Menus } from '../../../browser/menus.js'; import { ISessionsConfigurationService, ISessionScript } from './sessionsConfigurationService.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js'; // Menu IDs - exported for use in auxiliary bar part @@ -190,4 +191,5 @@ MenuRegistry.appendMenuItem(Menus.TitleBarRight, { icon: Codicon.play, group: 'navigation', order: 8, + when: IsAuxiliaryWindowContext.toNegated() }); diff --git a/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts b/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts new file mode 100644 index 0000000000000..1d762632f9f71 --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts @@ -0,0 +1,274 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from '../../../../base/browser/dom.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { toAction } from '../../../../base/common/actions.js'; +import { Radio } from '../../../../base/browser/ui/radio/radio.js'; +import { DropdownMenuActionViewItem } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { localize } from '../../../../nls.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; +import { IGitRepository } from '../../../../workbench/contrib/git/common/gitService.js'; +import { INewSession } from './newSession.js'; + +/** + * A dropdown menu action item that shows an icon, a text label, and a chevron. + */ +class LabeledDropdownMenuActionViewItem extends DropdownMenuActionViewItem { + protected override renderLabel(element: HTMLElement): null { + const classNames = typeof this.options.classNames === 'string' + ? this.options.classNames.split(/\s+/g).filter(s => !!s) + : (this.options.classNames ?? []); + if (classNames.length > 0) { + const icon = dom.append(element, dom.$('span')); + icon.classList.add('codicon', ...classNames); + } + + const label = dom.append(element, dom.$('span.sessions-chat-dropdown-label')); + label.textContent = this._action.label; + + dom.append(element, renderIcon(Codicon.chevronDown)); + + return null; + } +} + +// #region --- Session Target Picker --- + +/** + * A self-contained widget for selecting the session target (Local vs Cloud). + * Encapsulates state, events, and rendering. Can be placed anywhere in the view. + */ +export class SessionTargetPicker extends Disposable { + + private _selectedTarget: AgentSessionProviders; + private _allowedTargets: AgentSessionProviders[]; + + private readonly _onDidChangeTarget = this._register(new Emitter()); + readonly onDidChangeTarget: Event = this._onDidChangeTarget.event; + + private readonly _renderDisposables = this._register(new DisposableStore()); + private _container: HTMLElement | undefined; + + get selectedTarget(): AgentSessionProviders { + return this._selectedTarget; + } + + constructor( + allowedTargets: AgentSessionProviders[], + defaultTarget: AgentSessionProviders, + ) { + super(); + this._allowedTargets = allowedTargets; + this._selectedTarget = allowedTargets.includes(defaultTarget) + ? defaultTarget + : allowedTargets[0]; + } + + /** + * Renders the target radio (Local / Cloud) into the given container. + */ + render(container: HTMLElement): void { + this._container = container; + this._renderRadio(); + } + + updateAllowedTargets(targets: AgentSessionProviders[]): void { + if (targets.length === 0) { + return; + } + this._allowedTargets = targets; + if (!targets.includes(this._selectedTarget)) { + this._selectedTarget = targets[0]; + this._onDidChangeTarget.fire(this._selectedTarget); + } + if (this._container) { + this._renderRadio(); + } + } + + private _renderRadio(): void { + if (!this._container) { + return; + } + + this._renderDisposables.clear(); + dom.clearNode(this._container); + + if (this._allowedTargets.length === 0) { + return; + } + + const targets = [AgentSessionProviders.Background, AgentSessionProviders.Cloud].filter(t => this._allowedTargets.includes(t)); + const activeIndex = targets.indexOf(this._selectedTarget); + + const radio = new Radio({ + items: targets.map(target => ({ + text: getTargetLabel(target), + isActive: target === this._selectedTarget, + })), + }); + this._renderDisposables.add(radio); + this._container.appendChild(radio.domNode); + + if (activeIndex >= 0) { + radio.setActiveItem(activeIndex); + } + + this._renderDisposables.add(radio.onDidSelect(index => { + const target = targets[index]; + if (this._selectedTarget !== target) { + this._selectedTarget = target; + this._onDidChangeTarget.fire(target); + } + })); + } +} + +function getTargetLabel(provider: AgentSessionProviders): string { + switch (provider) { + case AgentSessionProviders.Local: + case AgentSessionProviders.Background: + return localize('chat.session.providerLabel.local', "Local"); + case AgentSessionProviders.Cloud: + return localize('chat.session.providerLabel.cloud', "Cloud"); + case AgentSessionProviders.Claude: + return 'Claude'; + case AgentSessionProviders.Codex: + return 'Codex'; + case AgentSessionProviders.Growth: + return 'Growth'; + } +} + +// #endregion + +// #region --- Isolation Mode Picker --- + +export type IsolationMode = 'worktree' | 'workspace'; + +/** + * A self-contained widget for selecting the isolation mode (Worktree vs Folder). + * Encapsulates state, events, and rendering. Can be placed anywhere in the view. + */ +export class IsolationModePicker extends Disposable { + + private _isolationMode: IsolationMode = 'worktree'; + private _newSession: INewSession | undefined; + private _repository: IGitRepository | undefined; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + private readonly _renderDisposables = this._register(new DisposableStore()); + private _container: HTMLElement | undefined; + private _dropdownContainer: HTMLElement | undefined; + + get isolationMode(): IsolationMode { + return this._isolationMode; + } + + constructor( + @IContextMenuService private readonly contextMenuService: IContextMenuService, + ) { + super(); + } + + /** + * Sets the pending session that this picker writes to. + */ + setNewSession(session: INewSession | undefined): void { + this._newSession = session; + } + + /** + * Sets the git repository. When undefined, worktree option is hidden + * and isolation mode falls back to 'workspace'. + */ + setRepository(repository: IGitRepository | undefined): void { + this._repository = repository; + if (repository) { + this._setMode('worktree'); + } else if (this._isolationMode === 'worktree') { + this._setMode('workspace'); + } + this._renderDropdown(); + } + + /** + * Renders the isolation mode dropdown into the given container. + */ + render(container: HTMLElement): void { + this._container = container; + this._dropdownContainer = dom.append(container, dom.$('.sessions-chat-local-mode-left')); + this._renderDropdown(); + } + + /** + * Shows or hides the picker. + */ + setVisible(visible: boolean): void { + if (this._container) { + this._container.style.visibility = visible ? '' : 'hidden'; + } + } + + private _renderDropdown(): void { + if (!this._dropdownContainer) { + return; + } + + this._renderDisposables.clear(); + dom.clearNode(this._dropdownContainer); + + const modeLabel = this._isolationMode === 'worktree' + ? localize('isolationMode.worktree', "Worktree") + : localize('isolationMode.folder', "Folder"); + const modeIcon = this._isolationMode === 'worktree' ? Codicon.worktree : Codicon.folder; + const isDisabled = !this._repository; + + const modeAction = toAction({ id: 'isolationMode', label: modeLabel, run: () => { } }); + const modeDropdown = this._renderDisposables.add(new LabeledDropdownMenuActionViewItem( + modeAction, + { + getActions: () => isDisabled ? [] : [ + toAction({ + id: 'isolationMode.worktree', + label: localize('isolationMode.worktree', "Worktree"), + checked: this._isolationMode === 'worktree', + run: () => this._setMode('worktree'), + }), + toAction({ + id: 'isolationMode.folder', + label: localize('isolationMode.folder', "Folder"), + checked: this._isolationMode === 'workspace', + run: () => this._setMode('workspace'), + }), + ], + }, + this.contextMenuService, + { classNames: [...ThemeIcon.asClassNameArray(modeIcon)] } + )); + const modeSlot = dom.append(this._dropdownContainer, dom.$('.sessions-chat-picker-slot')); + modeDropdown.render(modeSlot); + modeSlot.classList.toggle('disabled', isDisabled); + } + + private _setMode(mode: IsolationMode): void { + if (this._isolationMode !== mode) { + this._isolationMode = mode; + this._newSession?.setIsolationMode(mode); + this._onDidChange.fire(mode); + this._renderDropdown(); + } + } +} + +// #endregion diff --git a/src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts b/src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts index 57de7b183e99a..73601b49ce12d 100644 --- a/src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts +++ b/src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts @@ -36,7 +36,8 @@ Registry.as(Extensions.Configuration).registerDefaultCon 'workbench.startupEditor': 'none', 'workbench.tips.enabled': false, 'workbench.layoutControl.type': 'toggles', - 'workbench.editor.allowOpenInModalEditor': false, + 'workbench.editor.useModal': 'on', + 'workbench.editor.labelFormat': 'short', 'window.menuStyle': 'custom', 'window.dialogStyle': 'custom', diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts index 8ac2ffd7a0c6e..d3a7e96111910 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts @@ -16,15 +16,15 @@ import { ChatViewId, ChatViewPaneTarget, IChatWidgetService } from '../../../../ import { ChatViewPane } from '../../../../workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.js'; import { IChatSessionItem, IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; import { IChatService, IChatSendRequestOptions } from '../../../../workbench/contrib/chat/common/chatService/chatService.js'; -import { ChatAgentLocation } from '../../../../workbench/contrib/chat/common/constants.js'; +import { ChatAgentLocation, ChatModeKind } from '../../../../workbench/contrib/chat/common/constants.js'; import { IAgentSession, isAgentSession } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js'; import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js'; import { LocalChatSessionUri } from '../../../../workbench/contrib/chat/common/model/chatUri.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { IWorkspaceEditingService } from '../../../../workbench/services/workspaces/common/workspaceEditing.js'; import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js'; import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; +import { INewSession, LocalNewSession, RemoteNewSession } from '../../chat/browser/newSession.js'; export const IsNewChatSessionContext = new RawContextKey('isNewChatSession', true); @@ -80,11 +80,17 @@ export interface ISessionsManagementService { */ createNewPendingSession(pendingSessionResource: URI): Promise; + /** + * Create a pending session object for the given target type. + * Local sessions collect options locally; remote sessions notify the extension. + */ + createNewSessionForTarget(target: AgentSessionProviders, sessionResource: URI, defaultRepoUri?: URI): Promise; + /** * Open a new session, apply options, and send the initial request. - * This is the main entry point for the new-chat welcome widget. + * Looks up the session by resource URI and builds send options from it. */ - sendRequestForNewSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap, folderUri?: URI): Promise; + sendRequestForNewSession(sessionResource: URI): Promise; /** * Commit files in a worktree and refresh the agent sessions model @@ -102,6 +108,7 @@ export class SessionsManagementService extends Disposable implements ISessionsMa private readonly _activeSession = observableValue(this, undefined); readonly activeSession: IObservable = this._activeSession; + private readonly _newSessions = new Map(); private lastSelectedSession: URI | undefined; private readonly isNewChatSessionContext: IContextKey; @@ -115,7 +122,6 @@ export class SessionsManagementService extends Disposable implements ISessionsMa @ILogService private readonly logService: ILogService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, @IViewsService private readonly viewsService: IViewsService, @ICommandService private readonly commandService: ICommandService, ) { @@ -255,6 +261,19 @@ export class SessionsManagementService extends Disposable implements ISessionsMa return activeSessionItem; } + async createNewSessionForTarget(target: AgentSessionProviders, sessionResource: URI, defaultRepoUri?: URI): Promise { + const activeSessionItem = await this.createNewPendingSession(sessionResource); + + let newSession: INewSession; + if (target === AgentSessionProviders.Background || target === AgentSessionProviders.Local) { + newSession = new LocalNewSession(activeSessionItem, defaultRepoUri, this.chatSessionsService, this.logService); + } else { + newSession = new RemoteNewSession(activeSessionItem, target, this.chatSessionsService, this.logService); + } + this._newSessions.set(newSession.resource.toString(), newSession); + return newSession; + } + /** * Open an existing agent session - set it as active and reveal it. */ @@ -307,39 +326,45 @@ export class SessionsManagementService extends Disposable implements ISessionsMa this._activeSession.set(activeSessionItem, undefined); } - async sendRequestForNewSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap, folderUri?: URI): Promise { - if (LocalChatSessionUri.isLocalSession(sessionResource)) { - await this.sendLocalSession(sessionResource, query, sendOptions, folderUri); - } else { - await this.sendCustomSession(sessionResource, query, sendOptions, selectedOptions); + async sendRequestForNewSession(sessionResource: URI): Promise { + const session = this._newSessions.get(sessionResource.toString()); + if (!session) { + this.logService.error(`[SessionsManagementService] No new session found for resource: ${sessionResource.toString()}`); + return; } - } - /** - * Local sessions run directly through the ChatWidget. - * Set the workspace folder, open a fresh chat view, and submit via acceptInput. - */ - private async sendLocalSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, folderUri?: URI): Promise { - if (folderUri) { - await this.workspaceEditingService.updateFolders(0, this.workspaceContextService.getWorkspace().folders.length, [{ uri: folderUri }]); + const query = session.query; + if (!query) { + this.logService.error('[SessionsManagementService] No query set on session'); + return; } - await this.openSession(sessionResource); + const contribution = this.chatSessionsService.getChatSessionContribution(session.target); + const sendOptions: IChatSendRequestOptions = { + location: ChatAgentLocation.Chat, + userSelectedModelId: session.modelId, + modeInfo: { + kind: ChatModeKind.Agent, + isBuiltin: true, + modeInstructions: undefined, + modeId: 'agent', + applyCodeBlockSuggestionId: undefined, + }, + agentIdSilent: contribution?.type, + attachedContext: session.attachedContext, + }; - const widget = this.chatWidgetService.lastFocusedWidget; - if (widget) { - if (sendOptions.attachedContext?.length) { - widget.attachmentModel.addContext(...sendOptions.attachedContext); - } - widget.setInput(query); - widget.acceptInput(query); - } + await this.sendCustomSession(sessionResource, query, sendOptions, session.selectedOptions); + + // Clean up the session after sending + this._newSessions.delete(sessionResource.toString()); + session.dispose(); } /** * Custom sessions (worktree, cloud, etc.) go through the chat service. - * Apply selected options, send the request, then wait for the extension - * to create an agent session so it appears in the sidebar. + * Options have already been applied via setOption during session configuration. + * Send the request, then wait for the extension to create an agent session. */ private async sendCustomSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap): Promise { // 1. Open the session - loads the model and shows the ChatViewPane diff --git a/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts b/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts index 99d5b415b922e..6ed0a6d0acdd0 100644 --- a/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts @@ -3,12 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from '../../../base/common/cancellation.js'; +import { Sequencer } from '../../../base/common/async.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { Disposable } from '../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../base/common/map.js'; import { URI } from '../../../base/common/uri.js'; -import { IGitExtensionDelegate, IGitService, GitRef, GitRefQuery, GitRefType } from '../../contrib/git/common/gitService.js'; +import { GitRepository } from '../../contrib/git/browser/gitService.js'; +import { IGitExtensionDelegate, IGitService, GitRef, GitRefQuery, GitRefType, GitRepositoryState, GitBranch, IGitRepository } from '../../contrib/git/common/gitService.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; -import { ExtHostContext, ExtHostGitExtensionShape, GitRefTypeDto, MainContext, MainThreadGitExtensionShape } from '../common/extHost.protocol.js'; +import { ExtHostContext, ExtHostGitExtensionShape, GitRefTypeDto, GitRepositoryStateDto, MainContext, MainThreadGitExtensionShape } from '../common/extHost.protocol.js'; function toGitRefType(type: GitRefTypeDto): GitRefType { switch (type) { @@ -19,13 +21,35 @@ function toGitRefType(type: GitRefTypeDto): GitRefType { } } +function toGitRepositoryState(dto: GitRepositoryStateDto | undefined): GitRepositoryState { + return { + HEAD: dto?.HEAD ? { + type: toGitRefType(dto.HEAD.type), + name: dto.HEAD.name, + commit: dto.HEAD.commit, + remote: dto.HEAD.remote, + upstream: dto.HEAD.upstream, + ahead: dto.HEAD.ahead, + behind: dto.HEAD.behind, + } satisfies GitBranch : undefined, + }; +} + @extHostNamedCustomer(MainContext.MainThreadGitExtension) export class MainThreadGitExtensionService extends Disposable implements MainThreadGitExtensionShape, IGitExtensionDelegate { private readonly _proxy: ExtHostGitExtensionShape; + private readonly _openRepositorySequencer = new Sequencer(); + + private _repositoryHandles = new ResourceMap(); + private _repositories = new Map(); + + get repositories(): Iterable { + return this._repositories.values(); + } constructor( extHostContext: IExtHostContext, - @IGitService private readonly gitService: IGitService, + @IGitService private readonly gitService: IGitService ) { super(); @@ -44,13 +68,51 @@ export class MainThreadGitExtensionService extends Disposable implements MainThr } } - async openRepository(uri: URI): Promise { - const result = await this._proxy.$openRepository(uri); - return result ? URI.revive(result) : undefined; + private _getRepositoryByUri(uri: URI): IGitRepository | undefined { + const handle = this._repositoryHandles.get(uri); + return handle !== undefined ? this._repositories.get(handle) : undefined; + } + + async openRepository(uri: URI): Promise { + return this._openRepositorySequencer.queue(async () => { + // Check if we already have a repository for the given URI + const existingRepository = this._getRepositoryByUri(uri); + if (existingRepository) { + return existingRepository; + } + + // Open the repository + const result = await this._proxy.$openRepository(uri); + if (!result) { + return undefined; + } + + const repositoryRootUri = URI.revive(result.rootUri); + + // Check if we already have a repository for the given root + const existingRepositoryForRoot = this._getRepositoryByUri(repositoryRootUri); + if (existingRepositoryForRoot) { + return existingRepositoryForRoot; + } + + // Create a new repository and store it in the maps + const state = toGitRepositoryState(result.state); + const repository = new GitRepository(repositoryRootUri, state, this); + + this._repositories.set(result.handle, repository); + this._repositoryHandles.set(repositoryRootUri, result.handle); + + return repository; + }); } async getRefs(root: URI, query: GitRefQuery, token?: CancellationToken): Promise { - const result = await this._proxy.$getRefs(root, query, token); + const handle = this._repositoryHandles.get(root); + if (handle === undefined) { + return []; + } + + const result = await this._proxy.$getRefs(handle, query, token); if (token?.isCancellationRequested) { return []; @@ -61,4 +123,19 @@ export class MainThreadGitExtensionService extends Disposable implements MainThr type: toGitRefType(ref.type) } satisfies GitRef)); } + + async $onDidChangeRepository(handle: number): Promise { + const repository = this._repositories.get(handle); + if (!repository) { + return; + } + + const state = await this._proxy.$getRepositoryState(handle); + if (!state) { + return; + } + + // Update the repository state + repository.updateState(toGitRepositoryState(state)); + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 116d0b9f1fade..5504fae3fa645 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -119,6 +119,7 @@ export interface IMainContext extends IRPCProtocol { // --- main thread export interface MainThreadGitExtensionShape extends IDisposable { + $onDidChangeRepository(handle: number): Promise; } export interface MainThreadClipboardShape extends IDisposable { @@ -3476,10 +3477,31 @@ export interface GitRefDto { readonly revision: string; } +export interface GitRepositoryStateDto { + readonly HEAD?: GitBranchDto; +} + +export interface GitBranchDto { + readonly name?: string; + readonly commit?: string; + readonly type: GitRefTypeDto; + readonly remote?: string; + readonly upstream?: GitUpstreamRefDto; + readonly ahead?: number; + readonly behind?: number; +} + +export interface GitUpstreamRefDto { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + export interface ExtHostGitExtensionShape { $isGitExtensionAvailable(): Promise; - $openRepository(root: UriComponents): Promise; - $getRefs(root: UriComponents, query: GitRefQueryDto, token?: CancellationToken): Promise; + $openRepository(root: UriComponents): Promise<{ handle: number; rootUri: UriComponents; state: GitRepositoryStateDto } | undefined>; + $getRefs(handle: number, query: GitRefQueryDto, token?: CancellationToken): Promise; + $getRepositoryState(handle: number): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostGitExtensionService.ts b/src/vs/workbench/api/common/extHostGitExtensionService.ts index 91e267f62405f..8029798f00f0d 100644 --- a/src/vs/workbench/api/common/extHostGitExtensionService.ts +++ b/src/vs/workbench/api/common/extHostGitExtensionService.ts @@ -4,13 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import type * as vscode from 'vscode'; +import { Event } from '../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js'; +import { observableFromEvent, waitForState } from '../../../base/common/observable.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; import { IExtHostExtensionService } from './extHostExtensionService.js'; import { IExtHostRpcService } from './extHostRpcService.js'; -import { ExtHostGitExtensionShape, GitRefDto, GitRefQueryDto, GitRefTypeDto } from './extHost.protocol.js'; +import { ExtHostGitExtensionShape, GitBranchDto, GitRefDto, GitRefQueryDto, GitRefTypeDto, GitRepositoryStateDto, GitUpstreamRefDto, MainContext, MainThreadGitExtensionShape } from './extHost.protocol.js'; +import { ResourceMap } from '../../../base/common/map.js'; const GIT_EXTENSION_ID = 'vscode.git'; @@ -23,11 +26,50 @@ function toGitRefTypeDto(type: GitRefType): GitRefTypeDto { } } +function toGitBranchDto(branch: Branch): GitBranchDto { + return { + name: branch.name, + commit: branch.commit, + type: toGitRefTypeDto(branch.type), + remote: branch.remote, + upstream: branch.upstream ? toGitUpstreamRefDto(branch.upstream) : undefined, + ahead: branch.ahead, + behind: branch.behind, + }; +} + +function toGitUpstreamRefDto(upstream: UpstreamRef): GitUpstreamRefDto { + return { + remote: upstream.remote, + name: upstream.name, + commit: upstream.commit, + }; +} + interface Repository { readonly rootUri: vscode.Uri; + readonly state: RepositoryState; + getRefs(query: GitRefQuery, token?: vscode.CancellationToken): Promise; } +interface RepositoryState { + readonly HEAD: Branch | undefined; + readonly onDidChange: Event; +} + +interface Branch extends GitRef { + readonly upstream?: UpstreamRef; + readonly ahead?: number; + readonly behind?: number; +} + +interface UpstreamRef { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + interface GitRef { type: GitRefType; name?: string; @@ -65,14 +107,24 @@ export const IExtHostGitExtensionService = createDecorator(); + private readonly _repositoryByUri = new ResourceMap(); + private readonly _disposables = this._register(new DisposableStore()); constructor( - @IExtHostRpcService _extHostRpc: IExtHostRpcService, + @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostExtensionService private readonly _extHostExtensionService: IExtHostExtensionService, ) { super(); + + this._proxy = extHostRpc.getProxy(MainContext.MainThreadGitExtension); } async $isGitExtensionAvailable(): Promise { @@ -80,23 +132,59 @@ export class ExtHostGitExtensionService extends Disposable implements IExtHostGi return !!registry.getExtensionDescription(GIT_EXTENSION_ID); } - async $openRepository(uri: UriComponents): Promise { + async $openRepository(uri: UriComponents): Promise<{ handle: number; rootUri: UriComponents; state: GitRepositoryStateDto } | undefined> { const api = await this._ensureGitApi(); if (!api) { return undefined; } const repository = await api.openRepository(URI.revive(uri)); - return repository?.rootUri; - } + if (!repository) { + return undefined; + } - async $getRefs(uri: UriComponents, query: GitRefQueryDto, token?: vscode.CancellationToken): Promise { - const api = await this._ensureGitApi(); - if (!api) { - return []; + const existingHandle = this._repositoryByUri.get(repository.rootUri); + if (existingHandle !== undefined) { + return { + handle: existingHandle, + rootUri: repository.rootUri, + state: { + HEAD: repository.state.HEAD ? toGitBranchDto(repository.state.HEAD) : undefined + } + }; } - const repository = await api.openRepository(URI.revive(uri)); + // Ensure that the repository state is initialized + const repositoryStateObs = observableFromEvent(this, + repository.state.onDidChange, () => repository.state); + await waitForState(repositoryStateObs, state => !!state.HEAD); + + const repositoryState = repositoryStateObs.get(); + + // Store the repository and its handle in the maps + const handle = ExtHostGitExtensionService._handlePool++; + + this._repositories.set(handle, repository); + this._repositoryByUri.set(repository.rootUri, handle); + + // Subscribe to repository state changes + this._disposables.add(repository.state.onDidChange(() => { + this._proxy.$onDidChangeRepository(handle); + })); + + return { + handle, + rootUri: repository.rootUri, + state: { + HEAD: repositoryState.HEAD + ? toGitBranchDto(repositoryState.HEAD) + : undefined + } + }; + } + + async $getRefs(handle: number, query: GitRefQueryDto, token?: vscode.CancellationToken): Promise { + const repository = this._repositories.get(handle); if (!repository) { return []; } @@ -128,6 +216,16 @@ export class ExtHostGitExtensionService extends Disposable implements IExtHostGi } } + async $getRepositoryState(handle: number): Promise { + const repository = this._repositories.get(handle); + if (!repository) { + return undefined; + } + + const state = repository.state; + return { HEAD: state.HEAD ? toGitBranchDto(state.HEAD) : undefined }; + } + private async _ensureGitApi(): Promise { if (this._gitApi) { return this._gitApi; diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 5fd906f1d8585..961340c6548c7 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -27,7 +27,7 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from './editorQuickAccess.js'; import { SideBySideEditor } from './sideBySideEditor.js'; import { TextDiffEditor } from './textDiffEditor.js'; -import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, EditorPartModalContext, EditorPartModalMaximizedContext, EditorPartModalNavigationContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from '../../../common/contextkeys.js'; +import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, EditorPartModalContext, EditorPartModalMaximizedContext, EditorPartModalNavigationContext, IsSessionsWindowContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from '../../../common/contextkeys.js'; import { CloseDirection, EditorInputCapabilities, EditorsOrder, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, isEditorInputWithOptionsAndGroup } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js'; @@ -1422,7 +1422,8 @@ function registerModalEditorCommands(): void { menu: { id: MenuId.ModalEditorTitle, group: 'navigation', - order: 0 + order: 0, + when: IsSessionsWindowContext.negate() } }); } diff --git a/src/vs/workbench/browser/parts/editor/media/modalEditorPart.css b/src/vs/workbench/browser/parts/editor/media/modalEditorPart.css index 44c003884676a..6be747356305d 100644 --- a/src/vs/workbench/browser/parts/editor/media/modalEditorPart.css +++ b/src/vs/workbench/browser/parts/editor/media/modalEditorPart.css @@ -55,7 +55,7 @@ align-items: center; height: 32px; min-height: 32px; - padding: 0 8px 0 16px; + padding: 0 8px 0 10px; color: var(--vscode-titleBar-activeForeground); background-color: var(--vscode-titleBar-activeBackground); border-bottom: 1px solid var(--vscode-titleBar-border, transparent); @@ -67,8 +67,17 @@ font-weight: 500; color: var(--vscode-titleBar-activeForeground); overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + + .monaco-icon-label { + color: inherit; + height: 32px; + line-height: 32px; + + &::before, + & > .monaco-icon-label-iconpath { + height: 32px; + } + } } /* Modal Editor Navigation */ diff --git a/src/vs/workbench/browser/parts/editor/modalEditorPart.ts b/src/vs/workbench/browser/parts/editor/modalEditorPart.ts index bdbd911fe2884..1f6e93ba311e1 100644 --- a/src/vs/workbench/browser/parts/editor/modalEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/modalEditorPart.ts @@ -25,7 +25,8 @@ import { EditorPart } from './editorPart.js'; import { GroupDirection, GroupsOrder, IModalEditorPart, GroupActivationReason } from '../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { EditorPartModalContext, EditorPartModalMaximizedContext, EditorPartModalNavigationContext } from '../../../common/contextkeys.js'; -import { Verbosity } from '../../../common/editor.js'; +import { EditorResourceAccessor, SideBySideEditor, Verbosity } from '../../../common/editor.js'; +import { ResourceLabel } from '../../labels.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; import { mainWindow } from '../../../../base/browser/window.js'; @@ -123,7 +124,7 @@ export class ModalEditorPart { const headerElement = editorPartContainer.appendChild($('.modal-editor-header')); // Title element - const titleElement = append(headerElement, $('div.modal-editor-title')); + const titleElement = append(headerElement, $('div.modal-editor-title.show-file-icons')); titleElement.id = titleId; titleElement.textContent = ''; @@ -194,15 +195,30 @@ export class ModalEditorPart { menuOptions: { shouldForwardArgs: true } })); - disposables.add(Event.runAndSubscribe(modalEditorService.onDidActiveEditorChange, (() => { - - // Update title when active editor changes + // Create label + const label = disposables.add(scopedInstantiationService.createInstance(ResourceLabel, titleElement, {})); + disposables.add(Event.runAndSubscribe(modalEditorService.onDidActiveEditorChange, () => { const activeEditor = editorPart.activeGroup.activeEditor; - titleElement.textContent = activeEditor?.getTitle(Verbosity.MEDIUM) ?? ''; + if (activeEditor) { + const { labelFormat } = editorPart.partOptions; + + label.element.setResource( + { + resource: EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.BOTH }), + name: activeEditor.getName(), + description: activeEditor.getDescription(labelFormat === 'short' ? Verbosity.SHORT : labelFormat === 'long' ? Verbosity.LONG : Verbosity.MEDIUM) || '' + }, + { + icon: activeEditor.getIcon(), + extraClasses: activeEditor.getLabelExtraClasses(), + } + ); + } else { + label.element.clear(); + } - // Notify editor part that active editor changed editorPart.notifyActiveEditorChanged(); - }))); + })); // Handle double-click on header to toggle maximize disposables.add(addDisposableListener(headerElement, EventType.DBLCLICK, e => { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index b1c5637a750c3..a14964c0ef4b4 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -349,10 +349,16 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('revealIfOpen', "Controls whether an editor is revealed in any of the visible groups if opened. If disabled, an editor will prefer to open in the currently active editor group. If enabled, an already opened editor will be revealed instead of opened again in the currently active editor group. Note that there are some cases where this setting is ignored, such as when forcing an editor to open in a specific group or to the side of the currently active group."), 'default': false }, - 'workbench.editor.allowOpenInModalEditor': { - 'type': 'boolean', - 'description': localize('allowOpenInModalEditor', "Controls whether editors can be opened in a modal overlay. When enabled, certain editors such as Settings and Keyboard Shortcuts may open in a centered modal overlay instead of as a regular editor tab."), - 'default': product.quality !== 'stable', // TODO@bpasero figure out the default for stable + 'workbench.editor.useModal': { + 'type': 'string', + 'enum': ['off', 'default', 'on'], + 'enumDescriptions': [ + localize('useModal.off', "Editors never open in a modal overlay."), + localize('useModal.default', "Certain editors such as Settings and Keyboard Shortcuts may open in a centered modal overlay."), + localize('useModal.on', "All editors open in a centered modal overlay."), + ], + 'description': localize('useModal', "Controls whether editors open in a modal overlay."), + 'default': product.quality !== 'stable' ? 'default' : 'off', tags: ['experimental'], experiment: { mode: 'auto' diff --git a/src/vs/workbench/contrib/browserView/electron-browser/media/browser.css b/src/vs/workbench/contrib/browserView/electron-browser/media/browser.css index 0a1ea3099ab1a..e040c1a17416e 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/media/browser.css +++ b/src/vs/workbench/contrib/browserView/electron-browser/media/browser.css @@ -27,6 +27,7 @@ .actions-container { gap: 4px; + margin-right: 4px; } } diff --git a/src/vs/workbench/contrib/chat/browser/accessibility/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/accessibility/chatAccessibilityService.ts index a10346c9bb343..2691767520083 100644 --- a/src/vs/workbench/contrib/chat/browser/accessibility/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/accessibility/chatAccessibilityService.ts @@ -19,7 +19,7 @@ import { IHostService } from '../../../../services/host/browser/host.js'; import { AccessibilityVoiceSettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { ElicitationState, IChatElicitationRequest, IChatService } from '../../common/chatService/chatService.js'; import { IChatResponseViewModel } from '../../common/model/chatViewModel.js'; -import { ChatConfiguration } from '../../common/constants.js'; +import { ChatConfiguration, ChatNotificationMode } from '../../common/constants.js'; import { IChatAccessibilityService, IChatWidgetService } from '../chat.js'; import { ChatWidget } from '../widget/chatWidget.js'; import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; @@ -91,7 +91,8 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi } private async _showOSNotification(widget: ChatWidget, container: HTMLElement, responseContent: string): Promise { - if (!this._configurationService.getValue(ChatConfiguration.NotifyWindowOnResponseReceived)) { + const mode = this._configurationService.getValue(ChatConfiguration.NotifyWindowOnResponseReceived); + if (mode === ChatNotificationMode.Off) { return; } @@ -100,7 +101,8 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi return; } - if (targetWindow.document.hasFocus()) { + const isFocused = targetWindow.document.hasFocus(); + if (mode !== ChatNotificationMode.Always && isFocused) { return; } @@ -109,7 +111,10 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi return; } - await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + // Focus window in notify mode (flash taskbar/dock) if not already focused + if (!isFocused) { + await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + } // Dispose any previous unhandled notifications to avoid replacement/coalescing. this.toasts.clearAndDisposeAll(); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityActions.ts index 99bad32ce49b2..bc789fbed6df9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityActions.ts @@ -31,7 +31,7 @@ class AnnounceChatConfirmationAction extends Action2 { precondition: ChatContextKeys.enabled, f1: true, keybinding: { - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.ExternalExtension + 1, primary: KeyMod.CtrlCmd | KeyCode.KeyA | KeyMod.Shift, when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ChatContextKeys.Editing.hasQuestionCarousel.negate()) } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 88e50c6a49834..5843e1ee006f7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -893,7 +893,7 @@ export function registerChatActions() { f1: true, precondition: ChatContextKeys.inChatSession, keybinding: [{ - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.ExternalExtension + 1, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyA, when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.Editing.hasQuestionCarousel), }] diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 5542c4cad88be..45fe30497775d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -465,15 +465,16 @@ export class AttachContextAction extends Action2 { super({ id: 'workbench.action.chat.attachContext', title: localize2('workbench.action.chat.attachContext.label.2', "Add Context..."), - icon: Codicon.attach, + icon: Codicon.add, category: CHAT_CATEGORY, keybinding: { when: ContextKeyExpr.and(ChatContextKeys.inChatInput, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat)), primary: KeyMod.CtrlCmd | KeyCode.Slash, weight: KeybindingWeight.EditorContrib }, - menu: { + menu: [{ when: ContextKeyExpr.and( + ChatContextKeys.inQuickChat.negate(), ContextKeyExpr.or( ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat), ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditorInline), CTX_INLINE_CHAT_V2_ENABLED) @@ -483,10 +484,21 @@ export class AttachContextAction extends Action2 { ChatContextKeys.agentSupportsAttachments ) ), - id: MenuId.ChatInputAttachmentToolbar, + id: MenuId.ChatInput, group: 'navigation', - order: 3 - }, + order: 101 + }, { + when: ContextKeyExpr.and( + ChatContextKeys.inQuickChat, + ContextKeyExpr.or( + ChatContextKeys.lockedToCodingAgent.negate(), + ChatContextKeys.agentSupportsAttachments + ) + ), + id: MenuId.ChatExecute, + group: 'navigation', + order: -1 + }], }); } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 3743ead31ab9c..9dbb758b4068e 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -112,10 +112,6 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre h('div.agent-session-main-col', [ h('div.agent-session-title-row', [ h('div.agent-session-title@title'), - h('div.agent-session-status@statusContainer', [ - h('span.agent-session-status-provider-icon@statusProviderIcon'), - h('span.agent-session-status-time@statusTime') - ]), h('div.agent-session-title-toolbar@titleToolbar'), ]), h('div.agent-session-details-row', [ @@ -124,9 +120,15 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre h('span.agent-session-diff-added@addedSpan'), h('span.agent-session-diff-removed@removedSpan') ]), - h('div.agent-session-badge@badge'), - h('span.agent-session-separator@separator'), h('div.agent-session-description@description'), + h('div.agent-session-details-right', [ + h('div.agent-session-badge@badge'), + h('span.agent-session-separator@separator'), + h('div.agent-session-status@statusContainer', [ + h('span.agent-session-status-provider-icon@statusProviderIcon'), + h('span.agent-session-status-time@statusTime') + ]), + ]), ]) ]) ] @@ -219,9 +221,8 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre this.renderDescription(session, template, hasBadge); } - // Separator (dot between badge and description) - const hasDescription = template.description.textContent !== ''; - template.separator.classList.toggle('has-separator', hasBadge && hasDescription); + // Separator (dot between badge and timestamp) + template.separator.classList.toggle('has-separator', hasBadge); // Status this.renderStatus(session, template); @@ -508,7 +509,7 @@ export class AgentSessionSectionRenderer implements ICompressibleTreeRenderer { - static readonly ITEM_HEIGHT = 48; + static readonly ITEM_HEIGHT = 54; static readonly SECTION_HEIGHT = 26; getHeight(element: AgentSessionListItem): number { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css index b9863aae7dcbc..0432fdba3eb15 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css @@ -44,10 +44,6 @@ color: unset; } - .monaco-list-row.selected .agent-session-status { - color: unset; - } - .monaco-list:not(:focus) .monaco-list-row.selected .agent-session-details-row { color: var(--vscode-descriptionForeground); } @@ -66,7 +62,7 @@ } } - /* On hover or keyboard focus: show toolbar, hide status */ + /* On hover or keyboard focus: show toolbar */ .monaco-list-row:hover, .monaco-list-row.focused { @@ -74,15 +70,15 @@ display: block; } - .agent-session-status { - display: none; + .agent-session-title { + margin-right: 8px; } } .agent-session-item { display: flex; flex-direction: row; - padding: 6px 6px; + padding: 8px 6px; &.archived { color: var(--vscode-descriptionForeground); @@ -143,7 +139,7 @@ .agent-session-title-row { line-height: 17px; - padding-bottom: 4px; + padding-bottom: 6px; } .agent-session-details-row { @@ -227,21 +223,25 @@ flex: 1; overflow: hidden; white-space: nowrap; - margin-right: 16px; - mask-image: linear-gradient(to right, black calc(100% - 32px), transparent); - -webkit-mask-image: linear-gradient(to right, black calc(100% - 32px), transparent); } .agent-session-title { font-size: 13px; } + .agent-session-details-right { + display: flex; + align-items: center; + gap: 4px; + margin-left: auto; + white-space: nowrap; + flex-shrink: 0; + } + .agent-session-status { display: flex; align-items: center; font-variant-numeric: tabular-nums; - font-size: 12px; - color: var(--vscode-descriptionForeground); white-space: nowrap; .agent-session-status-provider-icon { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 9c86e875e9b01..54e5db4fda328 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -43,7 +43,7 @@ import { ChatTodoListService, IChatTodoListService } from '../common/tools/chatT import { ChatTransferService, IChatTransferService } from '../common/model/chatTransferService.js'; import { IChatVariablesService } from '../common/attachments/chatVariables.js'; import { ChatWidgetHistoryService, IChatWidgetHistoryService } from '../common/widget/chatWidgetHistoryService.js'; -import { AgentsControlClickBehavior, ChatConfiguration } from '../common/constants.js'; +import { AgentsControlClickBehavior, ChatConfiguration, ChatNotificationMode } from '../common/constants.js'; import { ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService } from '../common/ignoredFiles.js'; import { ILanguageModelsService, LanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelStatsService, LanguageModelStatsService } from '../common/languageModelStats.js'; @@ -332,10 +332,16 @@ configurationRegistry.registerConfiguration({ default: { } }, - 'chat.notifyWindowOnConfirmation': { - type: 'boolean', - description: nls.localize('chat.notifyWindowOnConfirmation', "Controls whether a chat session should present the user with an OS notification when a confirmation or question needs input while the window is not in focus. This includes a window badge as well as notification toast."), - default: true, + [ChatConfiguration.NotifyWindowOnConfirmation]: { + type: 'string', + enum: ['off', 'windowNotFocused', 'always'], + enumDescriptions: [ + nls.localize('chat.notifyWindowOnConfirmation.off', "Never show OS notifications for confirmations."), + nls.localize('chat.notifyWindowOnConfirmation.windowNotFocused', "Show OS notifications for confirmations when the window is not focused."), + nls.localize('chat.notifyWindowOnConfirmation.always', "Always show OS notifications for confirmations, even when the window is focused."), + ], + description: nls.localize('chat.notifyWindowOnConfirmation', "Controls whether a chat session should present the user with an OS notification when a confirmation or question needs input. This includes a window badge as well as notification toast."), + default: 'windowNotFocused', }, [ChatConfiguration.AutoReply]: { default: false, @@ -480,9 +486,15 @@ configurationRegistry.registerConfiguration({ description: nls.localize('chat.contextUsage.enabled', "Show the context window usage indicator in the chat input."), }, [ChatConfiguration.NotifyWindowOnResponseReceived]: { - type: 'boolean', - default: true, - description: nls.localize('chat.notifyWindowOnResponseReceived', "Controls whether a chat session should present the user with an OS notification when a response is received while the window is not in focus. This includes a window badge as well as notification toast."), + type: 'string', + enum: ['off', 'windowNotFocused', 'always'], + enumDescriptions: [ + nls.localize('chat.notifyWindowOnResponseReceived.off', "Never show OS notifications for responses."), + nls.localize('chat.notifyWindowOnResponseReceived.windowNotFocused', "Show OS notifications for responses when the window is not focused."), + nls.localize('chat.notifyWindowOnResponseReceived.always', "Always show OS notifications for responses, even when the window is focused."), + ], + default: 'windowNotFocused', + description: nls.localize('chat.notifyWindowOnResponseReceived', "Controls whether a chat session should present the user with an OS notification when a response is received. This includes a window badge as well as notification toast."), }, 'chat.checkpoints.enabled': { type: 'boolean', @@ -1225,6 +1237,28 @@ Registry.as(Extensions.ConfigurationMigration). return { value }; } }, + { + key: ChatConfiguration.NotifyWindowOnConfirmation, + migrateFn: (value: unknown) => { + if (value === true) { + return { value: ChatNotificationMode.WindowNotFocused }; + } else if (value === false) { + return { value: ChatNotificationMode.Off }; + } + return []; + } + }, + { + key: ChatConfiguration.NotifyWindowOnResponseReceived, + migrateFn: (value: unknown) => { + if (value === true) { + return { value: ChatNotificationMode.WindowNotFocused }; + } else if (value === false) { + return { value: ChatNotificationMode.Off }; + } + return []; + } + }, ]); class ChatResolverContribution extends Disposable { diff --git a/src/vs/workbench/contrib/chat/browser/chatTipService.ts b/src/vs/workbench/contrib/chat/browser/chatTipService.ts index a6635f79a2e91..8ffdf7c86a788 100644 --- a/src/vs/workbench/contrib/chat/browser/chatTipService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatTipService.ts @@ -24,6 +24,21 @@ import { localChatSessionType } from '../common/chatSessionsService.js'; import { IChatService } from '../common/chatService/chatService.js'; import { CreateSlashCommandsUsageTracker } from './createSlashCommandsUsageTracker.js'; import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; + +type ChatTipEvent = { + tipId: string; + action: string; + commandId?: string; +}; + +type ChatTipClassification = { + tipId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the tip.' }; + action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action performed on the tip (shown, dismissed, navigateNext, navigatePrevious, hidden, disabled, commandClicked).' }; + commandId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The command ID that was clicked, if applicable.' }; + owner: 'meganrogge'; + comment: 'Tracks user interactions with chat tips to understand which tips resonate and which are dismissed.'; +}; export const IChatTipService = createDecorator('chatTipService'); @@ -583,6 +598,7 @@ export class ChatTipService extends Disposable implements IChatTipService { private readonly _tracker: TipEligibilityTracker; private readonly _createSlashCommandsUsageTracker: CreateSlashCommandsUsageTracker; private _yoloModeEverEnabled: boolean; + private readonly _tipCommandListener = this._register(new MutableDisposable()); constructor( @IProductService private readonly _productService: IProductService, @@ -592,6 +608,8 @@ export class ChatTipService extends Disposable implements IChatTipService { @IInstantiationService instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @IChatEntitlementService chatEntitlementService: IChatEntitlementService, + @ICommandService private readonly _commandService: ICommandService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { super(); this._tracker = this._register(instantiationService.createInstance(TipEligibilityTracker, TIP_CATALOG)); @@ -630,6 +648,7 @@ export class ChatTipService extends Disposable implements IChatTipService { dismissTip(): void { if (this._shownTip) { + this._logTipTelemetry(this._shownTip.id, 'dismissed'); const dismissed = new Set(this._getDismissedTipIds()); dismissed.add(this._shownTip.id); this._storageService.store(ChatTipService._DISMISSED_TIP_KEY, JSON.stringify([...dismissed]), StorageScope.APPLICATION, StorageTarget.MACHINE); @@ -676,12 +695,18 @@ export class ChatTipService extends Disposable implements IChatTipService { } hideTip(): void { + if (this._shownTip) { + this._logTipTelemetry(this._shownTip.id, 'hidden'); + } this._shownTip = undefined; this._tipRequestId = undefined; this._onDidHideTip.fire(); } async disableTips(): Promise { + if (this._shownTip) { + this._logTipTelemetry(this._shownTip.id, 'disabled'); + } this._shownTip = undefined; this._tipRequestId = undefined; await this._configurationService.updateValue('chat.tips.enabled', false, ConfigurationTarget.APPLICATION); @@ -787,6 +812,9 @@ export class ChatTipService extends Disposable implements IChatTipService { this._tipRequestId = sourceId; this._shownTip = selectedTip; + this._logTipTelemetry(selectedTip.id, 'shown'); + this._trackTipCommandClicks(selectedTip); + return this._createTip(selectedTip); } @@ -820,9 +848,12 @@ export class ChatTipService extends Disposable implements IChatTipService { const idx = ((currentIndex + direction * i) % TIP_CATALOG.length + TIP_CATALOG.length) % TIP_CATALOG.length; const candidate = TIP_CATALOG[idx]; if (!dismissedIds.has(candidate.id) && this._isEligible(candidate, contextKeyService)) { + this._logTipTelemetry(this._shownTip.id, direction === 1 ? 'navigateNext' : 'navigatePrevious'); this._shownTip = candidate; this._tipRequestId = 'welcome'; this._storageService.store(ChatTipService._LAST_TIP_ID_KEY, candidate.id, StorageScope.APPLICATION, StorageTarget.USER); + this._logTipTelemetry(candidate.id, 'shown'); + this._trackTipCommandClicks(candidate); const tip = this._createTip(candidate); this._onDidNavigateTip.fire(tip); return tip; @@ -932,6 +963,27 @@ export class ChatTipService extends Disposable implements IChatTipService { }; } + private _logTipTelemetry(tipId: string, action: string, commandId?: string): void { + this._telemetryService.publicLog2('chatTip', { + tipId, + action, + commandId, + }); + } + + private _trackTipCommandClicks(tip: ITipDefinition): void { + this._tipCommandListener.clear(); + if (!tip.enabledCommands?.length) { + return; + } + const enabledCommandSet = new Set(tip.enabledCommands); + this._tipCommandListener.value = this._commandService.onDidExecuteCommand(e => { + if (enabledCommandSet.has(e.commandId) && this._shownTip?.id === tip.id) { + this._logTipTelemetry(tip.id, 'commandClicked', e.commandId); + } + }); + } + private _readApplicationWithProfileFallback(key: string): string | undefined { const applicationValue = this._storageService.get(key, StorageScope.APPLICATION); if (applicationValue) { diff --git a/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts b/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts index 2b4857787cfef..abece4b0a87a0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts @@ -17,6 +17,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IChatModel, IChatRequestNeedsInputInfo } from '../common/model/chatModel.js'; import { IChatService } from '../common/chatService/chatService.js'; +import { ChatConfiguration, ChatNotificationMode } from '../common/constants.js'; import { IChatWidgetService } from './chat.js'; import { AcceptToolConfirmationActionId, IToolConfirmationActionContext } from './actions/chatToolActions.js'; @@ -71,7 +72,8 @@ export class ChatWindowNotifier extends Disposable implements IWorkbenchContribu private async _notifyIfNeeded(sessionResource: URI, info: IChatRequestNeedsInputInfo): Promise { // Check configuration - if (!this._configurationService.getValue('chat.notifyWindowOnConfirmation')) { + const mode = this._configurationService.getValue(ChatConfiguration.NotifyWindowOnConfirmation); + if (mode === ChatNotificationMode.Off) { return; } @@ -79,16 +81,18 @@ export class ChatWindowNotifier extends Disposable implements IWorkbenchContribu const widget = this._chatWidgetService.getWidgetBySessionResource(sessionResource); const targetWindow = widget ? dom.getWindow(widget.domNode) : mainWindow; - // Only notify if window doesn't have focus - if (targetWindow.document.hasFocus()) { + const isFocused = targetWindow.document.hasFocus(); + if (mode !== ChatNotificationMode.Always && isFocused) { return; } // Clear any existing notification for this session this._clearNotification(sessionResource); - // Focus window in notify mode (flash taskbar/dock) - await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + // Focus window in notify mode (flash taskbar/dock) if not already focused + if (!isFocused) { + await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + } // Create OS notification const notificationTitle = info.title ? localize('chatTitle', "Chat: {0}", info.title) : localize('chat.untitledChat', "Untitled Chat"); diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 538f5d4ac2d68..ac323f512e4e3 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -86,7 +86,7 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider command: { title: localize('configure-tools.capitalized.ellipsis', "Configure Tools..."), id: this.cmdId, - arguments: [model, toolsAttr.range, toolsAttr.value.type === 'scalar', selectedTools, target] + arguments: [model, toolsAttr.value.range, toolsAttr.value.type === 'scalar', selectedTools, target] } }; return { lenses: [codeLens] }; diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts index 4134d04ad8231..27bef3f19c06b 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts @@ -2393,11 +2393,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } // Expand directory attachments: extract images as binary entries const resolvedImageVariables = await this._resolveDirectoryImageAttachments(requestInputs.attachedContext.asArray()); - - if (this.viewModel.sessionResource && !options.queue) { - // todo@connor4312: move chatAccessibilityService.acceptRequest to a refcount model to handle queue messages - this.chatAccessibilityService.acceptRequest(this._viewModel!.sessionResource); - } + const submittedSessionResource = this.viewModel.sessionResource; const result = await this.chatService.sendRequest(this.viewModel.sessionResource, requestInputs.input, { userSelectedModelId: this.input.currentLanguageModel, @@ -2414,10 +2410,6 @@ export class ChatWidget extends Disposable implements IChatWidget { pauseQueue: options?.alwaysQueue, }); - if (this.viewModel.sessionResource && !options.queue && ChatSendResult.isRejected(result)) { - this.chatAccessibilityService.disposeRequest(this.viewModel.sessionResource); - } - if (ChatSendResult.isRejected(result)) { return; } @@ -2431,24 +2423,23 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } - // If this was a queued request that just got dequeued, start the progress sound now - if (options.queue && this.viewModel?.sessionResource) { - this.chatAccessibilityService.acceptRequest(this.viewModel.sessionResource); - } - this._onDidSubmitAgent.fire({ agent: sent.data.agent, slashCommand: sent.data.slashCommand }); this.handleDelegationExitIfNeeded(this._lockedAgent, sent.data.agent); - sent.data.responseCompletePromise.then(() => { - const responses = this.viewModel?.getItems().filter(isResponseVM); - const lastResponse = responses?.[responses.length - 1]; - this.chatAccessibilityService.acceptResponse(this, this.container, lastResponse, this.viewModel?.sessionResource, options?.isVoiceInput); - if (lastResponse?.result?.nextQuestion) { - const { prompt, participant, command } = lastResponse.result.nextQuestion; - const question = formatChatQuestion(this.chatAgentService, this.location, prompt, participant, command); - if (question) { - this.input.setValue(question, false); + sent.data.responseCreatedPromise.then(() => { + // Only start accessibility progress once a real request/response model exists. + this.chatAccessibilityService.acceptRequest(submittedSessionResource); + sent.data.responseCompletePromise.then(() => { + const responses = this.viewModel?.getItems().filter(isResponseVM); + const lastResponse = responses?.[responses.length - 1]; + this.chatAccessibilityService.acceptResponse(this, this.container, lastResponse, submittedSessionResource, options?.isVoiceInput); + if (lastResponse?.result?.nextQuestion) { + const { prompt, participant, command } = lastResponse.result.nextQuestion; + const question = formatChatQuestion(this.chatAgentService, this.location, prompt, participant, command); + if (question) { + this.input.setValue(question, false); + } } - } + }); }); return sent.data.responseCreatedPromise; diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index 388ec5fb65589..32a286920ce08 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -13,7 +13,6 @@ import * as aria from '../../../../../../base/browser/ui/aria/aria.js'; import { ButtonWithIcon } from '../../../../../../base/browser/ui/button/button.js'; import { createInstantHoverDelegate } from '../../../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; -import { renderLabelWithIcons } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IAction } from '../../../../../../base/common/actions.js'; import { equals as arraysEqual } from '../../../../../../base/common/arrays.js'; import { DeferredPromise, RunOnceScheduler } from '../../../../../../base/common/async.js'; @@ -32,15 +31,13 @@ import { autorun, derived, derivedOpts, IObservable, ISettableObservable, observ import { isMacintosh } from '../../../../../../base/common/platform.js'; import { isEqual } from '../../../../../../base/common/resources.js'; import { ScrollbarVisibility } from '../../../../../../base/common/scrollable.js'; -import { assertType } from '../../../../../../base/common/types.js'; import { URI } from '../../../../../../base/common/uri.js'; import { IEditorConstructionOptions } from '../../../../../../editor/browser/config/editorConfiguration.js'; import { EditorExtensionsRegistry } from '../../../../../../editor/browser/editorExtensions.js'; import { CodeEditorWidget } from '../../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; -import { EditorLayoutInfo, EditorOptions, IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js'; +import { EditorLayoutInfo, EditorOption, EditorOptions, IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js'; import { IDimension } from '../../../../../../editor/common/core/2d/dimension.js'; import { IPosition } from '../../../../../../editor/common/core/position.js'; -import { IRange, Range } from '../../../../../../editor/common/core/range.js'; import { isLocation } from '../../../../../../editor/common/languages.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; @@ -83,7 +80,7 @@ import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEd import { InlineChatConfigKeys } from '../../../../inlineChat/common/inlineChat.js'; import { IChatViewTitleActionContext } from '../../../common/actions/chatActions.js'; import { ChatContextKeys } from '../../../common/actions/chatContextKeys.js'; -import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isStringVariableEntry } from '../../../common/attachments/chatVariableEntries.js'; +import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry } from '../../../common/attachments/chatVariableEntries.js'; import { ChatMode, IChatMode, IChatModeService } from '../../../common/chatModes.js'; import { IChatFollowup, IChatQuestionCarousel, IChatService, IChatSessionContext } from '../../../common/chatService/chatService.js'; import { agentOptionId, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsService, isIChatSessionFileChange2, localChatSessionType } from '../../../common/chatSessionsService.js'; @@ -104,7 +101,6 @@ import { ChatAttachmentModel } from '../../attachments/chatAttachmentModel.js'; import { IChatAttachmentWidgetRegistry } from '../../attachments/chatAttachmentWidgetRegistry.js'; import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../../attachments/chatAttachmentWidgets.js'; import { ChatImplicitContexts } from '../../attachments/chatImplicitContext.js'; -import { ImplicitContextAttachmentWidget } from '../../attachments/implicitContextAttachment.js'; import { IChatWidget, IChatWidgetViewModelChangeEvent, ISessionTypePickerDelegate, isIChatResourceViewContext, isIChatViewViewContext, IWorkspacePickerDelegate } from '../../chat.js'; import { ChatEditingShowChangesAction, ViewAllSessionChangesAction, ViewPreviousEditsAction } from '../../chatEditing/chatEditingActions.js'; import { resizeImage } from '../../chatImageUtils.js'; @@ -133,6 +129,7 @@ import { EnhancedModelPickerActionItem } from './modelPickerActionItem2.js'; const $ = dom.$; const INPUT_EDITOR_MAX_HEIGHT = 250; +const INPUT_EDITOR_MIN_VISIBLE_LINES = 2; const CachedLanguageModelsKey = 'chat.cachedLanguageModels.v2'; export interface IChatInputStyles { @@ -235,8 +232,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidClickOverlay = this._register(new Emitter()); readonly onDidClickOverlay: Event = this._onDidClickOverlay.event; - private readonly _implicitContextWidget: MutableDisposable = this._register(new MutableDisposable()); - private readonly _attachmentModel: ChatAttachmentModel; private _widget?: IChatWidget; public get attachmentModel(): ChatAttachmentModel { @@ -332,8 +327,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private executeToolbar!: MenuWorkbenchToolBar; private inputActionsToolbar!: MenuWorkbenchToolBar; - private addFilesToolbar: MenuWorkbenchToolBar | undefined; - private addFilesButton: AddFilesButton | undefined; + get inputEditor() { return this._inputEditor; @@ -1076,7 +1070,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge models.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name)); const sessionType = this.getCurrentSessionType(); - if (sessionType) { + if (sessionType && sessionType !== AgentSessionProviders.Local) { // Session has a specific chat session type - show only models that target // this session type, if any such models exist. return models.filter(entry => entry.metadata?.targetChatSessionType === sessionType && entry.metadata?.isUserSelectable); @@ -1944,13 +1938,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.h('.chat-getting-started-tip-container@chatGettingStartedTipContainer'), dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [ dom.h('.chat-input-container@inputContainer', [ - dom.h('.chat-context-usage-container@contextUsageWidgetContainer'), dom.h('.chat-editor-container@editorContainer'), - dom.h('.chat-input-toolbars@inputToolbars'), + dom.h('.chat-input-toolbars@inputToolbars', [ + dom.h('.chat-context-usage-container@contextUsageWidgetContainer'), + ]), ]), ]), dom.h('.chat-attachments-container@attachmentsContainer', [ - dom.h('.chat-attachment-toolbar@attachmentToolbar'), dom.h('.chat-attached-context@attachedContextContainer'), ]), dom.h('.interactive-input-followups@followupsContainer'), @@ -1966,13 +1960,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.h('.chat-getting-started-tip-container@chatGettingStartedTipContainer'), dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [ dom.h('.chat-input-container@inputContainer', [ - dom.h('.chat-context-usage-container@contextUsageWidgetContainer'), dom.h('.chat-attachments-container@attachmentsContainer', [ - dom.h('.chat-attachment-toolbar@attachmentToolbar'), dom.h('.chat-attached-context@attachedContextContainer'), ]), dom.h('.chat-editor-container@editorContainer'), - dom.h('.chat-input-toolbars@inputToolbars'), + dom.h('.chat-input-toolbars@inputToolbars', [ + dom.h('.chat-context-usage-container@contextUsageWidgetContainer'), + ]), ]), ]), ]); @@ -1995,7 +1989,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.attachmentsContainer = elements.attachmentsContainer; this.attachedContextContainer = elements.attachedContextContainer; const toolbarsContainer = elements.inputToolbars; - const attachmentToolbarContainer = elements.attachmentToolbar; this.chatEditingSessionWidgetContainer = elements.chatEditingSessionWidgetContainer; this.chatInputTodoListWidgetContainer = elements.chatInputTodoListWidgetContainer; this.chatGettingStartedTipContainer = elements.chatGettingStartedTipContainer; @@ -2004,7 +1997,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.chatInputWidgetsContainer = elements.chatInputWidgetsContainer; this.contextUsageWidgetContainer = elements.contextUsageWidgetContainer; - // Context usage widget + // Context usage widget — will be positioned in the toolbar after toolbars are created this.contextUsageWidget = this._register(this.instantiationService.createInstance(ChatContextUsageWidget)); this.contextUsageWidgetContainer.appendChild(this.contextUsageWidget.domNode); @@ -2337,23 +2330,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.renderAttachedContext(); })); - this.addFilesToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, attachmentToolbarContainer, MenuId.ChatInputAttachmentToolbar, { - telemetrySource: this.options.menus.telemetrySource, - label: true, - menuOptions: { shouldForwardArgs: true, renderShortTitle: true }, - hiddenItemStrategy: HiddenItemStrategy.NoHide, - hoverDelegate, - actionViewItemProvider: (action, options) => { - if (action.id === 'workbench.action.chat.attachContext') { - const viewItem = this.instantiationService.createInstance(AddFilesButton, this._attachmentModel, action, options); - viewItem.setShowLabel(this._attachmentModel.size === 0 && !this._implicitContextWidget.value?.hasRenderedContexts); - this.addFilesButton = viewItem; - return this.addFilesButton; - } - return undefined; - } - })); - this.addFilesToolbar.context = { widget, placeholder: localize('chatAttachFiles', 'Search for files and context to add to your request') }; this.renderAttachedContext(); const inputResizeObserver = this._register(new dom.DisposableResizeObserver(() => { @@ -2400,15 +2376,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); const attachments = [...this.attachmentModel.attachments.entries()]; - const hasAttachments = Boolean(attachments.length) || Boolean(this.implicitContext?.hasValue); - dom.setVisibility(Boolean(this.options.renderInputToolbarBelowInput || hasAttachments || (this.addFilesToolbar && !this.addFilesToolbar.isEmpty())), this.attachmentsContainer); + const hasAttachments = Boolean(attachments.length); + dom.setVisibility(Boolean(this.options.renderInputToolbarBelowInput || hasAttachments), this.attachmentsContainer); dom.setVisibility(hasAttachments, this.attachedContextContainer); if (!attachments.length) { this._indexOfLastAttachedContextDeletedWithKeyboard = -1; this._indexOfLastOpenedContext = -1; } - const isSuggestedEnabled = this.configurationService.getValue('chat.implicitContext.suggestedContext'); for (const [index, attachment] of attachments) { const resource = URI.isUri(attachment.value) ? attachment.value : isLocation(attachment.value) ? attachment.value.uri : undefined; @@ -2465,54 +2440,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); } - if (isSuggestedEnabled && this.implicitContext?.hasValue) { - this._implicitContextWidget.value = this.instantiationService.createInstance(ImplicitContextAttachmentWidget, () => this._widget, (targetUri: URI | undefined, targetRange: IRange | undefined, targetHandle: number | undefined) => this.isAttachmentAlreadyAttached(targetUri, targetRange, targetHandle, attachments.map(([, a]) => a)), this.implicitContext, this._contextResourceLabels, this._attachmentModel, container); - } else { - this._implicitContextWidget.clear(); - } - - this.addFilesButton?.setShowLabel(this._attachmentModel.size === 0 && !this._implicitContextWidget.value?.hasRenderedContexts); - this._indexOfLastOpenedContext = -1; } - private isAttachmentAlreadyAttached(targetUri: URI | undefined, targetRange: IRange | undefined, targetHandle: number | undefined, attachments: IChatRequestVariableEntry[]): boolean { - return attachments.some((attachment) => { - let uri: URI | undefined; - let range: IRange | undefined; - let handle: number | undefined; - - if (URI.isUri(attachment.value)) { - uri = attachment.value; - } else if (isLocation(attachment.value)) { - uri = attachment.value.uri; - range = attachment.value.range; - } else if (isStringVariableEntry(attachment)) { - uri = attachment.uri; - handle = attachment.handle; - } - - if ((handle !== undefined && targetHandle === undefined) || (handle === undefined && targetHandle !== undefined)) { - return false; - } - - if (handle !== undefined && targetHandle !== undefined && handle !== targetHandle) { - return false; - } - - if (!uri || !isEqual(uri, targetUri)) { - return false; - } - - // check if the exact range is already attached - if (targetRange) { - return range && Range.equalsRange(range, targetRange); - } - - return true; - }); - } - private handleAttachmentDeletion(e: KeyboardEvent | unknown, index: number, attachment: IChatRequestVariableEntry) { // Set focus to the next attached context item if deletion was triggered by a keystroke (vs a mouse click) if (dom.isKeyboardEvent(e)) { @@ -2555,20 +2485,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return; } - // eslint-disable-next-line no-restricted-syntax - const toolbar = this.addFilesToolbar?.getElement().querySelector('.action-label'); - if (!toolbar) { - return; - } - // eslint-disable-next-line no-restricted-syntax const attachments = Array.from(this.attachedContextContainer.querySelectorAll('.chat-attached-context-attachment')); if (!attachments.length) { return; } - attachments.unshift(toolbar); - const activeElement = dom.getWindow(this.attachmentsContainer).document.activeElement; const currentIndex = attachments.findIndex(attachment => attachment === activeElement); let newIndex = currentIndex; @@ -3029,7 +2951,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const initialEditorScrollWidth = this._inputEditor.getScrollWidth(); const newEditorWidth = width - data.inputPartHorizontalPadding - data.editorBorder - data.inputPartHorizontalPaddingInside - data.toolbarsWidth - data.sideToolbarWidth; - const inputEditorHeight = Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight); + const lineHeight = this._inputEditor.getOption(EditorOption.lineHeight); + const { top, bottom } = this._inputEditor.getOption(EditorOption.padding); + const inputEditorMinHeight = this.options.renderStyle === 'compact' ? 0 : (lineHeight * INPUT_EDITOR_MIN_VISIBLE_LINES) + top + bottom; + const inputEditorHeight = Math.max(inputEditorMinHeight, Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight)); const newDimension = { width: newEditorWidth, height: inputEditorHeight }; if (!this.previousInputEditorDimension || (this.previousInputEditorDimension.width !== newDimension.width || this.previousInputEditorDimension.height !== newDimension.height)) { // This layout call has side-effects that are hard to understand. eg if we are calling this inside a onDidChangeContent handler, this can trigger the next onDidChangeContent handler @@ -3063,7 +2988,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const inputToolbarWidth = this.cachedInputToolbarWidth = this.inputActionsToolbar.getItemsWidth(); const executeToolbarPadding = (this.executeToolbar.getItemsLength() - 1) * 4; const inputToolbarPadding = this.inputActionsToolbar.getItemsLength() ? (this.inputActionsToolbar.getItemsLength() - 1) * 4 : 0; - return executeToolbarWidth + executeToolbarPadding + (this.options.renderInputToolbarBelowInput ? 0 : inputToolbarWidth + inputToolbarPadding); + const contextUsageWidth = dom.getTotalWidth(this.contextUsageWidgetContainer); + return executeToolbarWidth + executeToolbarPadding + contextUsageWidth + (this.options.renderInputToolbarBelowInput ? 0 : inputToolbarWidth + inputToolbarPadding); }; return { @@ -3152,37 +3078,3 @@ class ChatSessionPickersContainerActionItem extends ActionViewItem { super.dispose(); } } - -class AddFilesButton extends ActionViewItem { - private showLabel: boolean | undefined; - - constructor(context: unknown, action: IAction, options: IActionViewItemOptions) { - super(context, action, { - ...options, - icon: false, - label: true, - keybindingNotRenderedWithLabel: true, - }); - } - - public setShowLabel(show: boolean): void { - this.showLabel = show; - this.updateLabel(); - } - - override render(container: HTMLElement): void { - container.classList.add('chat-attachment-button'); - super.render(container); - this.updateLabel(); - } - - protected override updateLabel(): void { - if (!this.label) { - return; - } - assertType(this.label); - this.label.classList.toggle('has-label', this.showLabel); - const message = this.showLabel ? `$(attach) ${this.action.label}` : `$(attach)`; - dom.reset(this.label, ...renderLabelWithIcons(message)); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts index d0a42f3645d52..bd38dd8e78750 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts @@ -124,7 +124,6 @@ export function buildModelPickerItems( selectedModelId: string | undefined, recentModelIds: string[], controlModels: IStringDictionary, - isProUser: boolean, currentVSCodeVersion: string, updateStateType: StateType, onSelect: (model: ILanguageModelChatMetadataAndIdentifier) => void, @@ -132,6 +131,7 @@ export function buildModelPickerItems( commandService: ICommandService, chatEntitlementService: IChatEntitlementService, ): IActionListItem[] { + const isPro = isProUser(chatEntitlementService.entitlement); const items: IActionListItem[] = []; let otherModels: ILanguageModelChatMetadataAndIdentifier[] = []; if (models.length === 0) { @@ -165,7 +165,7 @@ export function buildModelPickerItems( const resolveModel = (id: string) => allModelsMap.get(id) ?? modelsByMetadataId.get(id); const getUnavailableReason = (entry: IModelControlEntry): 'upgrade' | 'update' | 'admin' => { - if (!isProUser) { + if (!isPro) { return 'upgrade'; } if (entry.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) { @@ -245,6 +245,7 @@ export function buildModelPickerItems( } // Render promoted section: sorted alphabetically by name + let hasShownActionLink = false; if (promotedItems.length > 0) { promotedItems.sort((a, b) => { const aName = a.kind === 'available' ? a.model.metadata.name : a.entry.label; @@ -259,7 +260,11 @@ export function buildModelPickerItems( if (item.kind === 'available') { items.push(createModelItem(createModelAction(item.model, selectedModelId, onSelect), item.model)); } else { - items.push(createUnavailableModelItem(item.id, item.entry, item.reason, manageSettingsUrl, updateStateType)); + const showActionLink = item.reason === 'upgrade' ? !hasShownActionLink : true; + if (showActionLink && item.reason === 'upgrade') { + hasShownActionLink = true; + } + items.push(createUnavailableModelItem(item.id, item.entry, item.reason, manageSettingsUrl, updateStateType, undefined, showActionLink)); } } } @@ -301,7 +306,7 @@ export function buildModelPickerItems( for (const model of otherModels) { const entry = controlModels[model.metadata.id] ?? controlModels[model.identifier]; if (entry?.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) { - items.push(createUnavailableModelItem(model.metadata.id, entry, 'update', manageSettingsUrl, updateStateType, ModelPickerSection.Other)); + items.push(createUnavailableModelItem(model.metadata.id, entry, 'update', manageSettingsUrl, updateStateType, ModelPickerSection.Other, true)); } else { items.push(createModelItem(createModelAction(model, selectedModelId, onSelect, ModelPickerSection.Other), model)); } @@ -340,35 +345,6 @@ export function buildModelPickerItems( }); } - // Add sign-in / upgrade option if entitlement is anonymous / free / new user - const isNewOrAnonymousUser = !chatEntitlementService.sentiment.installed || - chatEntitlementService.entitlement === ChatEntitlement.Available || - chatEntitlementService.anonymous || - chatEntitlementService.entitlement === ChatEntitlement.Unknown; - if (isNewOrAnonymousUser || chatEntitlementService.entitlement === ChatEntitlement.Free) { - items.push({ kind: ActionListItemKind.Separator }); - items.push({ - item: { - id: 'moreModels', - enabled: true, - checked: false, - class: undefined, - tooltip: isNewOrAnonymousUser ? localize('chat.moreModels.tooltip', "Add Language Models") : localize('chat.morePremiumModels.tooltip', "Add Premium Models"), - label: isNewOrAnonymousUser ? localize('chat.moreModels', "Add Language Models") : localize('chat.morePremiumModels', "Add Premium Models"), - icon: Codicon.add, - run: () => { - const commandId = isNewOrAnonymousUser ? 'workbench.action.chat.triggerSetup' : 'workbench.action.chat.upgradePlan'; - commandService.executeCommand(commandId); - } - }, - kind: ActionListItemKind.Action, - label: isNewOrAnonymousUser ? localize('chat.moreModels', "Add Language Models") : localize('chat.morePremiumModels', "Add Premium Models"), - group: { title: '', icon: Codicon.add }, - hideIcon: false, - showAlways: true, - }); - } - return items; } @@ -379,11 +355,14 @@ function createUnavailableModelItem( manageSettingsUrl: string | undefined, updateStateType: StateType, section?: string, + showActionLink: boolean = true, ): IActionListItem { let description: string | MarkdownString | undefined; if (reason === 'upgrade') { - description = new MarkdownString(localize('chat.modelPicker.upgradeLink', "[Upgrade your plan](command:workbench.action.chat.upgradePlan \" \")"), { isTrusted: true }); + description = showActionLink + ? new MarkdownString(localize('chat.modelPicker.upgradeLink', "[Upgrade your plan](command:workbench.action.chat.upgradePlan \" \")"), { isTrusted: true }) + : undefined; } else if (reason === 'update') { description = localize('chat.modelPicker.updateDescription', "Update VS Code"); } else { @@ -417,8 +396,10 @@ function createUnavailableModelItem( kind: ActionListItemKind.Action, label: entry.label, description, + group: { title: '', icon: ThemeIcon.fromId(Codicon.blank.id) }, disabled: true, hideIcon: false, + className: 'chat-model-picker-unavailable', section, hover: { content: hoverContent }, }; @@ -540,7 +521,6 @@ export class ModelPickerWidget extends Disposable { this._selectedModel?.identifier, this._languageModelsService.getRecentlyUsedModelIds(), controlModelsForTier, - isPro, this._productService.version, this._updateService.state.type, onSelect, diff --git a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css index 24007074fe847..b10b74d14fff9 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -804,17 +804,16 @@ have to be updated for changes to the rules above, or to support more deeply nes position: relative; } -/* Context usage widget container - positioned at top right of chat input */ -.interactive-session .chat-input-container .chat-context-usage-container { - position: absolute; - top: 4px; - right: 6px; - z-index: 1; +/* Context usage widget container - positioned in the bottom toolbar */ +.interactive-session .chat-input-toolbars .chat-context-usage-container { + display: flex; + align-items: center; + flex-shrink: 0; + order: 1; } -/* Hide context usage widget in compact mode (quick chat) */ -.interactive-session .interactive-input-part.compact .chat-context-usage-container { - display: none; +.interactive-session .chat-input-toolbars > .chat-execute-toolbar { + order: 2; } .interactive-input-part:has(.chat-editing-session > .chat-editing-session-container) .chat-input-container, @@ -1007,7 +1006,6 @@ have to be updated for changes to the rules above, or to support more deeply nes flex-direction: row; gap: 4px; margin-top: 6px; - margin-right: 20px; flex-wrap: wrap; cursor: default; } @@ -1302,6 +1300,8 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .chat-input-toolbars { display: flex; + align-items: center; + gap: 6px; } .interactive-session .chat-input-toolbars :not(.responsive.chat-input-toolbar) .actions-container:first-child { @@ -1321,9 +1321,15 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .chat-input-toolbars > .chat-input-toolbar { + order: 0; overflow: hidden; min-width: 0px; width: 100%; + color: var(--vscode-icon-foreground); + + .monaco-action-bar .action-item .codicon { + color: var(--vscode-icon-foreground); + } .chat-input-picker-item { min-width: 0px; @@ -1382,7 +1388,7 @@ have to be updated for changes to the rules above, or to support more deeply nes padding: 3px 0px 3px 6px; display: flex; align-items: center; - color: var(--vscode-foreground); + color: var(--vscode-icon-foreground); } .monaco-workbench .interactive-session .chat-input-toolbar .chat-input-picker-item .action-label .codicon-chevron-down, @@ -1401,6 +1407,26 @@ have to be updated for changes to the rules above, or to support more deeply nes max-width: 100%; } +.monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item .codicon, +.monaco-workbench .interactive-session .chat-input-toolbars .action-label .codicon { + color: var(--vscode-icon-foreground) !important; +} + +.monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item.disabled .codicon, +.monaco-workbench .interactive-session .chat-input-toolbars .action-label.disabled .codicon { + color: var(--vscode-disabledForeground) !important; +} + +.action-widget .monaco-list-row.chat-model-picker-unavailable .description a, +.action-widget .monaco-list-row.chat-model-picker-unavailable .description a:visited { + color: var(--vscode-textLink-foreground); +} + +.action-widget .monaco-list-row.chat-model-picker-unavailable .description a:hover, +.action-widget .monaco-list-row.chat-model-picker-unavailable .description a:active { + color: var(--vscode-textLink-activeForeground); +} + .interactive-session .chat-input-toolbars .codicon-debug-stop { color: var(--vscode-icon-foreground) !important; } @@ -1583,7 +1609,6 @@ have to be updated for changes to the rules above, or to support more deeply nes padding: 8px 0 0 0 } -.action-item.chat-attachment-button .action-label, .interactive-session .chat-attached-context .chat-attached-context-attachment { display: flex; overflow: hidden; @@ -1596,40 +1621,11 @@ have to be updated for changes to the rules above, or to support more deeply nes width: fit-content; } -.action-item.chat-attachment-button .action-label { - padding: 0 4px; -} - .interactive-session .interactive-list .chat-attached-context .chat-attached-context-attachment { font-family: var(--vscode-chat-font-family, inherit); font-size: var(--vscode-chat-font-size-body-xs); } -.action-item.chat-attachment-button > .action-label > .codicon { - font-size: 14px; - height: auto; -} - -.interactive-session .action-item.chat-attachment-button .action-label:not(.has-label) { - display: flex; - align-items: center; - justify-content: center; - padding: 2px 4px 2px 4px; - height: fit-content; - gap: 0; - border: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-background, transparent)); - border-radius: 4px; - box-sizing: border-box; - - .codicon { - width: 14px; - } -} - -.action-item.chat-attachment-button .codicon { - font-size: 14px; -} - .action-item.chat-mcp { min-width: 22px !important; @@ -1732,11 +1728,6 @@ have to be updated for changes to the rules above, or to support more deeply nes gap: 4px; } -.interactive-session .chat-attachment-toolbar .actions-container { - gap: 4px; - flex-wrap: wrap; -} - .interactive-session .interactive-input-part.compact .chat-attached-context { padding-bottom: 0px; display: flex; diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatContextUsageWidget.ts b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatContextUsageWidget.ts index 36c6db7e50d6d..b20f39c7cf678 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatContextUsageWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatContextUsageWidget.ts @@ -27,71 +27,55 @@ import { KeyCode } from '../../../../../../base/common/keyCodes.js'; const $ = dom.$; /** - * A reusable circular progress indicator that displays a pie chart. - * The pie fills clockwise from the top based on the percentage value. + * A reusable circular progress indicator that displays a ring. + * The ring fills clockwise from the top based on the percentage value. */ export class CircularProgressIndicator { readonly domNode: SVGSVGElement; - private readonly progressPie: SVGPathElement; + private readonly progressCircle: SVGCircleElement; + private readonly circumference: number; private static readonly CENTER_X = 18; private static readonly CENTER_Y = 18; - private static readonly RADIUS = 16; + private static readonly RADIUS = 14; constructor() { + const r = CircularProgressIndicator.RADIUS; + this.circumference = 2 * Math.PI * r; + this.domNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.domNode.setAttribute('viewBox', '0 0 36 36'); this.domNode.classList.add('circular-progress'); - // Background circle (outline only) + // Background circle const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); bgCircle.setAttribute('cx', String(CircularProgressIndicator.CENTER_X)); bgCircle.setAttribute('cy', String(CircularProgressIndicator.CENTER_Y)); - bgCircle.setAttribute('r', String(CircularProgressIndicator.RADIUS)); + bgCircle.setAttribute('r', String(r)); bgCircle.classList.add('progress-bg'); this.domNode.appendChild(bgCircle); - // Progress pie (filled arc) - this.progressPie = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - this.progressPie.classList.add('progress-pie'); - this.domNode.appendChild(this.progressPie); + // Progress arc (stroke-based ring) + this.progressCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + this.progressCircle.setAttribute('cx', String(CircularProgressIndicator.CENTER_X)); + this.progressCircle.setAttribute('cy', String(CircularProgressIndicator.CENTER_Y)); + this.progressCircle.setAttribute('r', String(r)); + this.progressCircle.classList.add('progress-arc'); + this.progressCircle.setAttribute('stroke-dasharray', String(this.circumference)); + this.progressCircle.setAttribute('stroke-dashoffset', String(this.circumference)); + this.domNode.appendChild(this.progressCircle); } /** - * Updates the pie chart to display the given percentage (0-100). - * @param percentage The percentage of the pie to fill (clamped to 0-100) + * Updates the ring to display the given percentage (0-100). + * @param percentage The percentage of the ring to fill (clamped to 0-100) */ setProgress(percentage: number): void { - const cx = CircularProgressIndicator.CENTER_X; - const cy = CircularProgressIndicator.CENTER_Y; - const r = CircularProgressIndicator.RADIUS; - - if (percentage >= 100) { - // Full circle - use a circle element's path equivalent - this.progressPie.setAttribute('d', `M ${cx} ${cy - r} A ${r} ${r} 0 1 1 ${cx - 0.001} ${cy - r} Z`); - } else if (percentage <= 0) { - // Empty - no path - this.progressPie.setAttribute('d', ''); - } else { - // Calculate the arc endpoint - const angle = (percentage / 100) * 360; - const radians = (angle - 90) * (Math.PI / 180); // Start from top (-90 degrees) - const x = cx + r * Math.cos(radians); - const y = cy + r * Math.sin(radians); - const largeArcFlag = angle > 180 ? 1 : 0; - - // Create pie slice path: move to center, line to top, arc to endpoint, close - const d = [ - `M ${cx} ${cy}`, // Move to center - `L ${cx} ${cy - r}`, // Line to top - `A ${r} ${r} 0 ${largeArcFlag} 1 ${x} ${y}`, // Arc to endpoint - 'Z' // Close path back to center - ].join(' '); - - this.progressPie.setAttribute('d', d); - } + const clamped = Math.max(0, Math.min(100, percentage)); + const offset = this.circumference - (clamped / 100) * this.circumference; + this.progressCircle.setAttribute('stroke-dashoffset', String(offset)); } } diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatContextUsageWidget.css b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatContextUsageWidget.css index 9906a0b8171b6..c3abb1332f0de 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatContextUsageWidget.css +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatContextUsageWidget.css @@ -8,6 +8,7 @@ align-items: center; justify-content: center; height: 22px; + width: 22px; flex-shrink: 0; cursor: pointer; padding: 3px; @@ -19,8 +20,8 @@ display: flex; align-items: center; justify-content: center; - width: 16px; - height: 16px; + width: 14px; + height: 14px; flex-shrink: 0; } @@ -46,22 +47,26 @@ } .chat-context-usage-widget .progress-bg { - fill: transparent; - stroke: var(--vscode-icon-foreground); - stroke-width: 2; - opacity: 0.4; + fill: none; + stroke: var(--vscode-disabledForeground); + stroke-width: 4; + opacity: 0.5; } -.chat-context-usage-widget .progress-pie { - fill: var(--vscode-icon-foreground); - opacity: 0.8; +.chat-context-usage-widget .progress-arc { + fill: none; + stroke: var(--vscode-descriptionForeground); + stroke-width: 4; + stroke-linecap: round; + transform: rotate(-90deg); + transform-origin: center; pointer-events: none; } -.chat-context-usage-widget.warning .progress-pie { - fill: var(--vscode-editorWarning-foreground); +.chat-context-usage-widget.warning .progress-arc { + stroke: var(--vscode-editorWarning-foreground); } -.chat-context-usage-widget.error .progress-pie { - fill: var(--vscode-editorError-foreground); +.chat-context-usage-widget.error .progress-arc { + stroke: var(--vscode-editorError-foreground); } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 693f7c1f9da74..ed3f2318acc61 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -38,6 +38,7 @@ export enum ChatConfiguration { ThinkingPhrases = 'chat.agent.thinking.phrases', AutoExpandToolFailures = 'chat.tools.autoExpandFailures', TodosShowWidget = 'chat.tools.todos.showWidget', + NotifyWindowOnConfirmation = 'chat.notifyWindowOnConfirmation', NotifyWindowOnResponseReceived = 'chat.notifyWindowOnResponseReceived', ChatViewSessionsEnabled = 'chat.viewSessions.enabled', ChatViewSessionsGrouping = 'chat.viewSessions.grouping', @@ -90,6 +91,12 @@ export enum CollapsedToolsDisplayMode { Always = 'always', } +export enum ChatNotificationMode { + Off = 'off', + WindowNotFocused = 'windowNotFocused', + Always = 'always', +} + export enum AgentsControlClickBehavior { Default = 'default', Cycle = 'cycle', diff --git a/src/vs/workbench/contrib/chat/common/model/chatModelStore.ts b/src/vs/workbench/contrib/chat/common/model/chatModelStore.ts index 268dba2ec107c..588be79a82851 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatModelStore.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatModelStore.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from '../../../../../base/common/event.js'; -import { DisposableStore, IDisposable, IReference, ReferenceCollection } from '../../../../../base/common/lifecycle.js'; +import { Disposable, IDisposable, IReference, ReferenceCollection } from '../../../../../base/common/lifecycle.js'; import { ObservableMap } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; @@ -27,17 +27,17 @@ export interface ChatModelStoreDelegate { willDisposeModel: (model: ChatModel) => Promise; } -export class ChatModelStore extends ReferenceCollection implements IDisposable { - private readonly _store = new DisposableStore(); +export class ChatModelStore extends Disposable implements IDisposable { + private readonly _refCollection: ReferenceCollection; private readonly _models = new ObservableMap(); private readonly _modelsToDispose = new Set(); private readonly _pendingDisposals = new Set>(); - private readonly _onDidDisposeModel = this._store.add(new Emitter()); + private readonly _onDidDisposeModel = this._register(new Emitter()); public readonly onDidDisposeModel = this._onDidDisposeModel.event; - private readonly _onDidCreateModel = this._store.add(new Emitter()); + private readonly _onDidCreateModel = this._register(new Emitter()); public readonly onDidCreateModel = this._onDidCreateModel.event; constructor( @@ -45,6 +45,16 @@ export class ChatModelStore extends ReferenceCollection implements ID @ILogService private readonly logService: ILogService, ) { super(); + + const self = this; + this._refCollection = new class extends ReferenceCollection { + protected createReferencedObject(key: string, props?: IStartSessionProps): ChatModel { + return self.createReferencedObject(key, props); + } + protected destroyReferencedObject(key: string, object: ChatModel): void { + return self.destroyReferencedObject(key, object); + } + }(); } public get observable() { @@ -71,14 +81,14 @@ export class ChatModelStore extends ReferenceCollection implements ID if (!this._models.has(key)) { return undefined; } - return this.acquire(key); + return this._refCollection.acquire(key); } public acquireOrCreate(props: IStartSessionProps): IReference { - return this.acquire(this.toKey(props.sessionResource), props); + return this._refCollection.acquire(this.toKey(props.sessionResource), props); } - protected createReferencedObject(key: string, props?: IStartSessionProps): ChatModel { + private createReferencedObject(key: string, props?: IStartSessionProps): ChatModel { this._modelsToDispose.delete(key); const existingModel = this._models.get(key); if (existingModel) { @@ -99,7 +109,7 @@ export class ChatModelStore extends ReferenceCollection implements ID return model; } - protected destroyReferencedObject(key: string, object: ChatModel): void { + private destroyReferencedObject(key: string, object: ChatModel): void { this._modelsToDispose.add(key); const promise = this.doDestroyReferencedObject(key, object); this._pendingDisposals.add(promise); @@ -135,8 +145,8 @@ export class ChatModelStore extends ReferenceCollection implements ID return uri.toString(); } - dispose(): void { - this._store.dispose(); + override dispose(): void { + super.dispose(); this._models.forEach(model => model.dispose()); } } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts index df645e77bea4d..b1c79a5e6e621 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts @@ -31,6 +31,8 @@ import { CreateSlashCommandsUsageTracker } from '../../browser/createSlashComman import { ChatRequestSlashCommandPart } from '../../common/requestParser/chatParserTypes.js'; import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js'; import { Range } from '../../../../../editor/common/core/range.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js'; class MockContextKeyServiceWithRulesMatching extends MockContextKeyService { override contextMatchesRules(rules: ContextKeyExpression): boolean { @@ -99,6 +101,7 @@ suite('ChatTipService', () => { instantiationService.stub(ILanguageModelToolsService, testDisposables.add(new MockLanguageModelToolsService())); instantiationService.stub(IChatEntitlementService, new TestChatEntitlementService()); instantiationService.stub(IChatService, new MockChatService()); + instantiationService.stub(ITelemetryService, NullTelemetryService); }); test('returns a welcome tip', () => { @@ -924,6 +927,135 @@ suite('ChatTipService', () => { }); } + test('logs telemetry when tip is shown', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + const shownEvents = events.filter(e => e.data.action === 'shown'); + assert.strictEqual(shownEvents.length, 1, 'Should log exactly one shown event'); + assert.strictEqual(shownEvents[0].eventName, 'chatTip'); + assert.strictEqual(shownEvents[0].data.tipId, tip.id); + }); + + test('logs telemetry when tip is dismissed', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + service.dismissTip(); + + const dismissEvents = events.filter(e => e.data.action === 'dismissed'); + assert.strictEqual(dismissEvents.length, 1, 'Should log exactly one dismissed event'); + assert.strictEqual(dismissEvents[0].data.tipId, tip.id); + }); + + test('logs telemetry when navigating tips', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + const nextTip = service.navigateToNextTip(); + assert.ok(nextTip); + + const navigateEvents = events.filter(e => e.data.action === 'navigateNext'); + assert.strictEqual(navigateEvents.length, 1, 'Should log one navigateNext event'); + assert.strictEqual(navigateEvents[0].data.tipId, tip.id, 'navigateNext should log the tip being navigated away from'); + + const shownEvents = events.filter(e => e.data.action === 'shown'); + assert.strictEqual(shownEvents.length, 2, 'Should log shown for initial and navigated tip'); + assert.strictEqual(shownEvents[1].data.tipId, nextTip.id); + }); + + test('logs telemetry when tip command is clicked', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + if (tip.enabledCommands?.length) { + commandExecutedEmitter.fire({ commandId: tip.enabledCommands[0], args: [] }); + + const clickEvents = events.filter(e => e.data.action === 'commandClicked'); + assert.strictEqual(clickEvents.length, 1, 'Should log one commandClicked event'); + assert.strictEqual(clickEvents[0].data.tipId, tip.id); + assert.strictEqual(clickEvents[0].data.commandId, tip.enabledCommands[0]); + } else { + assert.fail('Tip has no enabled commands; cannot test command click telemetry'); + } + }); + + test('logs telemetry when tip is hidden', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + service.hideTip(); + + const hiddenEvents = events.filter(e => e.data.action === 'hidden'); + assert.strictEqual(hiddenEvents.length, 1, 'Should log one hidden event'); + assert.strictEqual(hiddenEvents[0].data.tipId, tip.id); + }); + + test('logs telemetry when tips are disabled', async () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + await service.disableTips(); + + const disabledEvents = events.filter(e => e.data.action === 'disabled'); + assert.strictEqual(disabledEvents.length, 1, 'Should log one disabled event'); + assert.strictEqual(disabledEvents[0].data.tipId, tip.id); + }); + test('excludeWhenSettingsChanged checks workspaceValue', () => { const workspaceConfigService = new TestConfigurationService(); const originalInspect = workspaceConfigService.inspect.bind(workspaceConfigService); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/promptFileRewriter.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/promptFileRewriter.test.ts new file mode 100644 index 0000000000000..aa5843f4b4623 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/promptFileRewriter.test.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI } from '../../../../../../base/common/uri.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { createTextModel } from '../../../../../../editor/test/common/testTextModel.js'; +import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { ContextKeyService } from '../../../../../../platform/contextkey/browser/contextKeyService.js'; +import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; +import { ChatConfiguration } from '../../../common/constants.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../../common/tools/languageModelToolsService.js'; +import { LanguageModelToolsService } from '../../../browser/tools/languageModelToolsService.js'; +import { IPromptsService } from '../../../common/promptSyntax/service/promptsService.js'; +import { getLanguageIdForPromptsType, PromptsType } from '../../../common/promptSyntax/promptTypes.js'; +import { PromptFileParser, PromptHeaderAttributes, parseCommaSeparatedList } from '../../../common/promptSyntax/promptFileParser.js'; +import { PromptFileRewriter } from '../../../browser/promptSyntax/promptFileRewriter.js'; + +suite('PromptFileRewriter', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let instaService: TestInstantiationService; + + setup(async () => { + const testConfigService = new TestConfigurationService(); + testConfigService.setUserConfiguration(ChatConfiguration.ExtensionToolsEnabled, true); + instaService = workbenchInstantiationService({ + contextKeyService: () => disposables.add(new ContextKeyService(testConfigService)), + configurationService: () => testConfigService + }, disposables); + + const toolService = disposables.add(instaService.createInstance(LanguageModelToolsService)); + + const testTool1 = { id: 'testTool1', displayName: 'tool1', toolReferenceName: 'testTool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(testTool1)); + const testTool2 = { id: 'testTool2', displayName: 'tool2', toolReferenceName: 'testTool2', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(testTool2)); + + instaService.set(ILanguageModelToolsService, toolService); + + const parser = new PromptFileParser(); + instaService.stub(IPromptsService, { + getParsedPromptFile(model: import('../../../../../../editor/common/model.js').ITextModel) { + return parser.parse(model.uri, model.getValue()); + }, + }); + }); + + test('rewriteTools preserves tools key when using value range (array syntax)', () => { + const content = [ + '---', + 'description: "Test agent"', + 'tools: [testTool1]', + '---', + ].join('\n'); + const languageId = getLanguageIdForPromptsType(PromptsType.agent); + const uri = URI.parse('test:///test.agent.md'); + const model = disposables.add(createTextModel(content, languageId, undefined, uri)); + + const parser = new PromptFileParser(); + const parsed = parser.parse(uri, content); + const toolsAttr = parsed.header!.getAttribute(PromptHeaderAttributes.tools); + assert.ok(toolsAttr); + + const toolService = instaService.get(ILanguageModelToolsService); + const enablementMap = toolService.toToolAndToolSetEnablementMap(['testTool1', 'testTool2'], undefined); + + const rewriter = instaService.createInstance(PromptFileRewriter); + rewriter.rewriteTools(model, enablementMap, toolsAttr.value.range, false); + + const result = model.getValue(); + assert.ok(result.includes('tools:'), `Expected 'tools:' key to be preserved, but got: ${result}`); + assert.ok(result.includes('testTool1'), `Expected 'testTool1' in result: ${result}`); + assert.ok(result.includes('testTool2'), `Expected 'testTool2' in result: ${result}`); + }); + + test('rewriteTools preserves tools key when using value range (string syntax)', () => { + const content = [ + '---', + 'description: "Test agent"', + 'tools: testTool1', + '---', + ].join('\n'); + const languageId = getLanguageIdForPromptsType(PromptsType.agent); + const uri = URI.parse('test:///test.agent.md'); + const model = disposables.add(createTextModel(content, languageId, undefined, uri)); + + const parser = new PromptFileParser(); + const parsed = parser.parse(uri, content); + const toolsAttr = parsed.header!.getAttribute(PromptHeaderAttributes.tools); + assert.ok(toolsAttr); + + let value = toolsAttr.value; + if (value.type === 'scalar') { + value = parseCommaSeparatedList(value); + } + + const toolService = instaService.get(ILanguageModelToolsService); + const enablementMap = toolService.toToolAndToolSetEnablementMap(['testTool1', 'testTool2'], undefined); + + const rewriter = instaService.createInstance(PromptFileRewriter); + rewriter.rewriteTools(model, enablementMap, toolsAttr.value.range, true); + + const result = model.getValue(); + assert.ok(result.includes('tools:'), `Expected 'tools:' key to be preserved, but got: ${result}`); + assert.ok(result.includes('testTool1'), `Expected 'testTool1' in result: ${result}`); + assert.ok(result.includes('testTool2'), `Expected 'testTool2' in result: ${result}`); + }); + + test('rewriteTools with attribute range would destroy the tools key (regression)', () => { + const content = [ + '---', + 'description: "Test agent"', + 'tools: [testTool1]', + '---', + ].join('\n'); + const languageId = getLanguageIdForPromptsType(PromptsType.agent); + const uri = URI.parse('test:///test.agent.md'); + disposables.add(createTextModel(content, languageId, undefined, uri)); + + const parser = new PromptFileParser(); + const parsed = parser.parse(uri, content); + const toolsAttr = parsed.header!.getAttribute(PromptHeaderAttributes.tools); + assert.ok(toolsAttr); + + // Verify that the attribute range is larger than the value range + assert.ok( + toolsAttr.range.startColumn < toolsAttr.value.range.startColumn || + toolsAttr.range.startLineNumber < toolsAttr.value.range.startLineNumber, + 'Attribute range should start before value range (includes the key)' + ); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/browser/widget/input/chatModelPicker.test.ts b/src/vs/workbench/contrib/chat/test/browser/widget/input/chatModelPicker.test.ts index 527259458ab1f..8f2f67e90b6ab 100644 --- a/src/vs/workbench/contrib/chat/test/browser/widget/input/chatModelPicker.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/widget/input/chatModelPicker.test.ts @@ -5,6 +5,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { Codicon } from '../../../../../../../base/common/codicons.js'; import { IStringDictionary } from '../../../../../../../base/common/collections.js'; import { MarkdownString } from '../../../../../../../base/common/htmlContent.js'; import { ActionListItemKind, IActionListItem } from '../../../../../../../platform/actionWidget/browser/actionList.js'; @@ -15,12 +16,16 @@ import { buildModelPickerItems } from '../../../../browser/widget/input/chatMode import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, IModelControlEntry } from '../../../../common/languageModels.js'; import { ChatEntitlement, IChatEntitlementService } from '../../../../../../services/chat/common/chatEntitlementService.js'; -const stubChatEntitlementService: Partial = { - entitlement: ChatEntitlement.Pro, - sentiment: { installed: true } as IChatEntitlementService['sentiment'], - isInternal: false, - anonymous: false, -}; +function createStubEntitlementService(opts?: { entitlement?: ChatEntitlement; isInternal?: boolean; anonymous?: boolean }): IChatEntitlementService { + return { + entitlement: opts?.entitlement ?? ChatEntitlement.Pro, + sentiment: { installed: true } as IChatEntitlementService['sentiment'], + isInternal: opts?.isInternal ?? false, + anonymous: opts?.anonymous ?? false, + } as IChatEntitlementService; +} + +const stubChatEntitlementService = createStubEntitlementService(); function createModel(id: string, name: string, vendor = 'copilot'): ILanguageModelChatMetadataAndIdentifier { return { @@ -68,25 +73,29 @@ function callBuild( selectedModelId?: string; recentModelIds?: string[]; controlModels?: IStringDictionary; - isProUser?: boolean; + entitlement?: ChatEntitlement; currentVSCodeVersion?: string; updateStateType?: StateType; manageSettingsUrl?: string; + anonymous?: boolean; } = {}, ): IActionListItem[] { const onSelect = () => { }; + const entitlementService = createStubEntitlementService({ + entitlement: opts.entitlement ?? ChatEntitlement.Pro, + anonymous: opts.anonymous ?? false, + }); return buildModelPickerItems( models, opts.selectedModelId, opts.recentModelIds ?? [], opts.controlModels ?? {}, - opts.isProUser ?? true, opts.currentVSCodeVersion ?? '1.100.0', opts.updateStateType ?? StateType.Idle, onSelect, opts.manageSettingsUrl, stubCommandService, - stubChatEntitlementService as IChatEntitlementService, + entitlementService, ); } @@ -172,7 +181,7 @@ suite('buildModelPickerItems', () => { controlModels: { 'missing-model': { label: 'Missing Model', exists: false }, }, - isProUser: false, + entitlement: ChatEntitlement.Free, }); const actions = getActionItems(items); const unavailable = actions.find(a => a.label === 'Missing Model'); @@ -187,7 +196,6 @@ suite('buildModelPickerItems', () => { controlModels: { 'missing-model': { label: 'Missing Model', minVSCodeVersion: '2.0.0', exists: false }, }, - isProUser: true, currentVSCodeVersion: '1.90.0', }); const actions = getActionItems(items); @@ -203,7 +211,6 @@ suite('buildModelPickerItems', () => { controlModels: { 'missing-model': { label: 'Missing Model', exists: false }, }, - isProUser: true, }); const actions = getActionItems(items); const unavailable = actions.find(a => a.label === 'Missing Model'); @@ -232,7 +239,7 @@ suite('buildModelPickerItems', () => { controlModels: { 'premium-model': { label: 'Premium Model', featured: true, exists: false }, }, - isProUser: false, + entitlement: ChatEntitlement.Free, }); const actions = getActionItems(items); const unavailable = actions.find(a => a.label === 'Premium Model'); @@ -246,7 +253,6 @@ suite('buildModelPickerItems', () => { controlModels: { 'premium-model': { label: 'Premium Model', featured: true, exists: false }, }, - isProUser: true, }); const actions = getActionItems(items); const unavailable = actions.find(a => a.label === 'Premium Model'); @@ -311,7 +317,7 @@ suite('buildModelPickerItems', () => { controlModels: { 'missing-model': { label: 'Missing Model', exists: false }, }, - isProUser: false, + entitlement: ChatEntitlement.Free, }); const actions = getActionItems(items); // Auto, then GPT-4o (available), then Missing Model (unavailable) @@ -435,13 +441,12 @@ suite('buildModelPickerItems', () => { undefined, [], {}, - true, '1.100.0', StateType.Idle, onSelect, undefined, stubCommandService, - stubChatEntitlementService as IChatEntitlementService, + stubChatEntitlementService, ); const gptItem = getActionItems(items).find(a => a.label === 'GPT-4o'); assert.ok(gptItem?.item); @@ -516,14 +521,13 @@ suite('buildModelPickerItems', () => { [auto], undefined, ['missing-model'], - { 'missing-model': { label: 'Missing Model' } }, - true, + { 'missing-model': { label: 'Missing Model' } as IModelControlEntry }, '1.100.0', StateType.Idle, () => { }, 'https://aka.ms/github-copilot-settings', stubCommandService, - stubChatEntitlementService as IChatEntitlementService, + stubChatEntitlementService, ); const adminItem = getActionItems(items).find(a => a.label === 'Missing Model'); @@ -533,4 +537,81 @@ suite('buildModelPickerItems', () => { assert.ok(description instanceof MarkdownString); assert.ok(description.value.includes('https://aka.ms/github-copilot-settings')); }); + + test('unavailable models keep indentation with blank icon', () => { + const auto = createAutoModel(); + const items = callBuild([auto], { + recentModelIds: ['missing-model'], + controlModels: { + 'missing-model': { label: 'Missing Model' } as IModelControlEntry, + }, + entitlement: ChatEntitlement.Free, + }); + + const unavailable = getActionItems(items).find(a => a.label === 'Missing Model'); + assert.ok(unavailable); + assert.strictEqual(unavailable.hideIcon, false); + assert.strictEqual(unavailable.group?.icon?.id, Codicon.blank.id); + }); + + test('anonymous user sees upgrade description only on first unavailable model', () => { + const auto = createAutoModel(); + const items = callBuild([auto], { + recentModelIds: ['model-a', 'model-b'], + controlModels: { + 'model-a': { label: 'Model A', featured: true, exists: false }, + 'model-b': { label: 'Model B', featured: true, exists: false }, + }, + anonymous: true, + entitlement: ChatEntitlement.Unknown, + }); + const actions = getActionItems(items); + const disabledItems = actions.filter(a => a.disabled); + assert.strictEqual(disabledItems.length, 2); + assert.ok(disabledItems[0].description instanceof MarkdownString); + assert.ok(disabledItems[0].description.value.includes('Upgrade')); + assert.strictEqual(disabledItems[1].description, undefined); + }); + + test('free user sees upgrade description only on first unavailable model', () => { + const auto = createAutoModel(); + const items = callBuild([auto], { + recentModelIds: ['model-a', 'model-b'], + controlModels: { + 'model-a': { label: 'Model A', featured: true, exists: false }, + 'model-b': { label: 'Model B', featured: true, exists: false }, + }, + entitlement: ChatEntitlement.Free, + }); + const actions = getActionItems(items); + const disabledItems = actions.filter(a => a.disabled); + assert.strictEqual(disabledItems.length, 2); + assert.ok(disabledItems[0].description instanceof MarkdownString); + assert.ok(disabledItems[0].description.value.includes('Upgrade')); + assert.strictEqual(disabledItems[1].description, undefined); + }); + + test('anonymous user model selection triggers onSelect normally', () => { + const auto = createAutoModel(); + const modelA = createModel('gpt-4o', 'GPT-4o'); + let selectedModel: ILanguageModelChatMetadataAndIdentifier | undefined; + const onSelect = (m: ILanguageModelChatMetadataAndIdentifier) => { selectedModel = m; }; + const anonymousEntitlementService = createStubEntitlementService({ entitlement: ChatEntitlement.Unknown, anonymous: true }); + const items = buildModelPickerItems( + [auto, modelA], + undefined, + [], + {}, + '1.100.0', + StateType.Idle, + onSelect, + undefined, + stubCommandService, + anonymousEntitlementService, + ); + const gptItem = getActionItems(items).find(a => a.label === 'GPT-4o'); + assert.ok(gptItem?.item); + gptItem.item.run(); + assert.strictEqual(selectedModel?.identifier, modelA.identifier); + }); }); diff --git a/src/vs/workbench/contrib/git/browser/gitService.ts b/src/vs/workbench/contrib/git/browser/gitService.ts index 8b6cab3183580..9dfb27c8fad6f 100644 --- a/src/vs/workbench/contrib/git/browser/gitService.ts +++ b/src/vs/workbench/contrib/git/browser/gitService.ts @@ -3,23 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Sequencer } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { BugIndicatingError } from '../../../../base/common/errors.js'; import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { ResourceMap } from '../../../../base/common/map.js'; import { URI } from '../../../../base/common/uri.js'; -import { IGitService, IGitExtensionDelegate, GitRef, GitRefQuery, IGitRepository } from '../common/gitService.js'; +import { IGitService, IGitExtensionDelegate, GitRef, GitRefQuery, IGitRepository, GitRepositoryState } from '../common/gitService.js'; +import { ISettableObservable, observableValueOpts } from '../../../../base/common/observable.js'; +import { structuralEquals } from '../../../../base/common/equals.js'; export class GitService extends Disposable implements IGitService { declare readonly _serviceBrand: undefined; private _delegate: IGitExtensionDelegate | undefined; - private readonly _openRepositorySequencer = new Sequencer(); - private readonly _repositories = new ResourceMap(); get repositories(): Iterable { - return this._repositories.values(); + return this._delegate?.repositories ?? []; } setDelegate(delegate: IGitExtensionDelegate): IDisposable { @@ -33,48 +31,37 @@ export class GitService extends Disposable implements IGitService { this._delegate = delegate; return toDisposable(() => { - this._repositories.clear(); this._delegate = undefined; }); } async openRepository(uri: URI): Promise { - return this._openRepositorySequencer.queue(async () => { - if (!this._delegate) { - return undefined; - } - - // Check whether we have an opened repository for the uri - let repository = this._repositories.get(uri); - if (repository) { - return repository; - } + if (!this._delegate) { + return undefined; + } - // Open the repository to get the repository root - const root = await this._delegate.openRepository(uri); - if (!root) { - return undefined; - } + return this._delegate.openRepository(uri); + } +} - const rootUri = URI.revive(root); +export class GitRepository extends Disposable implements IGitRepository { + readonly rootUri: URI; - // Check whether we have an opened repository for the root - repository = this._repositories.get(rootUri); - if (repository) { - return repository; - } + readonly state: ISettableObservable; + updateState(state: GitRepositoryState): void { + this.state.set(state, undefined); + } - // Create a new repository - repository = new GitRepository(this._delegate, rootUri); - this._repositories.set(rootUri, repository); + constructor( + rootUri: URI, + initialState: GitRepositoryState, + private readonly delegate: IGitExtensionDelegate + ) { + super(); - return repository; - }); + this.rootUri = rootUri; + this.state = observableValueOpts({ owner: this, equalsFn: structuralEquals }, initialState); } -} - -export class GitRepository implements IGitRepository { - constructor(private readonly delegate: IGitExtensionDelegate, readonly rootUri: URI) { } async getRefs(query: GitRefQuery, token?: CancellationToken): Promise { return this.delegate.getRefs(this.rootUri, query, token); diff --git a/src/vs/workbench/contrib/git/common/gitService.ts b/src/vs/workbench/contrib/git/common/gitService.ts index 89f76d083cf00..353686d452ab5 100644 --- a/src/vs/workbench/contrib/git/common/gitService.ts +++ b/src/vs/workbench/contrib/git/common/gitService.ts @@ -5,7 +5,8 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; -import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { IObservable } from '../../../../base/common/observable.js'; +import { URI } from '../../../../base/common/uri.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; export enum GitRefType { @@ -28,14 +29,36 @@ export interface GitRefQuery { readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate'; } +export interface GitRepositoryState { + readonly HEAD?: GitBranch; +} + +export interface GitBranch extends GitRef { + readonly upstream?: GitUpstreamRef; + readonly ahead?: number; + readonly behind?: number; +} + +export interface GitUpstreamRef { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + export interface IGitRepository { readonly rootUri: URI; + + readonly state: IObservable; + updateState(state: GitRepositoryState): void; + getRefs(query: GitRefQuery, token?: CancellationToken): Promise; } export interface IGitExtensionDelegate { - getRefs(uri: UriComponents, query?: GitRefQuery, token?: CancellationToken): Promise; - openRepository(uri: UriComponents): Promise; + readonly repositories: Iterable; + openRepository(uri: URI): Promise; + + getRefs(root: URI, query?: GitRefQuery, token?: CancellationToken): Promise; } export const IGitService = createDecorator('gitService'); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 059b63a92c5e5..e765fb499e529 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -551,7 +551,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts index 55901336b328d..e48d807dec883 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts @@ -254,4 +254,16 @@ suite('TerminalSandboxService - allowTrustedDomains', () => { const config = JSON.parse(configContent); strictEqual(config.network.allowedDomains.length, 0, 'Should have no domains (* filtered out)'); }); + + test('should add ripgrep bin directory to PATH when wrapping command', async () => { + const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService)); + await sandboxService.getSandboxConfigPath(); + + const wrappedCommand = sandboxService.wrapCommand('echo test'); + + ok( + wrappedCommand.includes('PATH') && wrappedCommand.includes('ripgrep'), + 'Wrapped command should include PATH modification with ripgrep' + ); + }); }); diff --git a/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css b/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css index edc3e1d193d49..a28e8a835929b 100644 --- a/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css +++ b/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css @@ -168,31 +168,31 @@ * Each pair forms a visual row. * Left column items need to move up by floor((index-1)/2) rows * Right column items need to move right and up by (index/2) rows - * Row height is 44px. + * Row height is 54px (must match AgentSessionsListDelegate.ITEM_HEIGHT). */ /* Left column items (odd positions): move up to form 2-column layout */ /* Item 3: move up 1 row */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(3) { - transform: translateY(-44px); + transform: translateY(-54px); } /* Item 5: move up 2 rows */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(5) { - transform: translateY(-88px); + transform: translateY(-108px); } /* Right column items (even positions): move right and up */ /* Item 2: move right, up 1 row */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(2) { - transform: translateX(100%) translateY(-44px); + transform: translateX(100%) translateY(-54px); } /* Item 4: move right, up 2 rows */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(4) { - transform: translateX(100%) translateY(-88px); + transform: translateX(100%) translateY(-108px); } /* Item 6: move right, up 3 rows */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(6) { - transform: translateX(100%) translateY(-132px); + transform: translateX(100%) translateY(-162px); } /* Style individual session items in the welcome page */ diff --git a/src/vs/workbench/services/accounts/browser/defaultAccount.ts b/src/vs/workbench/services/accounts/browser/defaultAccount.ts index 3fba2439fa500..9cc1b8bc96089 100644 --- a/src/vs/workbench/services/accounts/browser/defaultAccount.ts +++ b/src/vs/workbench/services/accounts/browser/defaultAccount.ts @@ -28,8 +28,10 @@ import { distinct } from '../../../../base/common/arrays.js'; import { equals } from '../../../../base/common/objects.js'; import { IDefaultChatAgent } from '../../../../base/common/product.js'; import { IRequestContext } from '../../../../base/parts/request/common/request.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { localize2 } from '../../../../nls.js'; +import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; interface IDefaultAccountConfig { readonly preferredExtensions: string[]; @@ -853,4 +855,17 @@ class DefaultAccountProviderContribution extends Disposable implements IWorkbenc } } +registerAction2(class extends Action2 { + constructor() { + super({ + id: DEFAULT_ACCOUNT_SIGN_IN_COMMAND, + title: localize2('signIn', 'Sign In'), + }); + } + async run(accessor: ServicesAccessor): Promise { + const defaultAccountService = accessor.get(IDefaultAccountService); + await defaultAccountService.signIn(); + } +}); + registerWorkbenchContribution2(DefaultAccountProviderContribution.ID, DefaultAccountProviderContribution, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/services/editor/common/editorGroupFinder.ts b/src/vs/workbench/services/editor/common/editorGroupFinder.ts index f46747726b722..af149b8102874 100644 --- a/src/vs/workbench/services/editor/common/editorGroupFinder.ts +++ b/src/vs/workbench/services/editor/common/editorGroupFinder.ts @@ -29,15 +29,16 @@ export function findGroup(accessor: ServicesAccessor, editor: EditorInputWithOpt const group = doFindGroup(editor, preferredGroup, editorGroupService, configurationService); if (group instanceof Promise) { - return group.then(group => handleGroupResult(group, editor, preferredGroup, editorGroupService)); + return group.then(group => handleGroupResult(group, editor, preferredGroup, editorGroupService, configurationService)); } - return handleGroupResult(group, editor, preferredGroup, editorGroupService); + return handleGroupResult(group, editor, preferredGroup, editorGroupService, configurationService); } -function handleGroupResult(group: IEditorGroup, editor: EditorInputWithOptions | IUntypedEditorInput, preferredGroup: PreferredGroup | undefined, editorGroupService: IEditorGroupsService): [IEditorGroup, EditorActivation | undefined] { +function handleGroupResult(group: IEditorGroup, editor: EditorInputWithOptions | IUntypedEditorInput, preferredGroup: PreferredGroup | undefined, editorGroupService: IEditorGroupsService, configurationService: IConfigurationService): [IEditorGroup, EditorActivation | undefined] { const modalEditorPart = editorGroupService.activeModalEditorPart; - if (modalEditorPart && preferredGroup !== MODAL_GROUP) { + const modalEditorMode = configurationService.getValue('workbench.editor.useModal'); + if (modalEditorPart && preferredGroup !== MODAL_GROUP && modalEditorMode !== 'on') { // Only allow to open in modal group if MODAL_GROUP is explicitly requested group = handleModalEditorPart(group, editor, modalEditorPart, editorGroupService); } @@ -124,7 +125,7 @@ function doFindGroup(input: EditorInputWithOptions | IUntypedEditorInput, prefer } // Group: Modal (gated behind a setting) - else if (preferredGroup === MODAL_GROUP && configurationService.getValue('workbench.editor.allowOpenInModalEditor')) { + else if (preferredGroup === MODAL_GROUP && configurationService.getValue('workbench.editor.useModal') !== 'off') { group = editorGroupService.createModalEditorPart(options?.modal) .then(part => part.activeGroup); } @@ -174,6 +175,12 @@ function doFindGroup(input: EditorInputWithOptions | IUntypedEditorInput, prefer } } + // Force modal editor part: redirect to the modal group when setting is 'on' + if (!group && configurationService.getValue('workbench.editor.useModal') === 'on') { + group = editorGroupService.createModalEditorPart(options?.modal) + .then(part => part.activeGroup); + } + // Fallback to active group if target not valid but avoid // locked editor groups unless editor is already opened there if (!group) { diff --git a/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts b/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts index f33d165e59f0a..1c0db5ecf5835 100644 --- a/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts +++ b/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts @@ -16,6 +16,8 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes import { Registry } from '../../../../../platform/registry/common/platform.js'; import { MODAL_GROUP, MODAL_GROUP_TYPE } from '../../common/editorService.js'; import { findGroup } from '../../common/editorGroupFinder.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; suite('Modal Editor Group', () => { @@ -504,5 +506,82 @@ suite('Modal Editor Group', () => { modalPart.close(); }); + suite('useModal: on', () => { + + test('findGroup creates modal and returns its active group', async () => { + const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench.editor.useModal', 'on'); + instantiationService.stub(IConfigurationService, configurationService); + const parts = await createEditorParts(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, parts); + + // findGroup with undefined preferredGroup should create modal and return its group + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const result = instantiationService.invokeFunction(accessor => findGroup(accessor, { resource: input.resource }, undefined)); + + // Should return a promise (async modal creation) + assert.ok(result instanceof Promise); + const [group] = await result; + + // The group should be in the modal part + assert.ok(parts.activeModalEditorPart); + assert.strictEqual(group.id, parts.activeModalEditorPart.activeGroup.id); + + parts.activeModalEditorPart.close(); + }); + + test('findGroup does not auto-close modal', async () => { + const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench.editor.useModal', 'on'); + instantiationService.stub(IConfigurationService, configurationService); + const parts = await createEditorParts(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, parts); + + // Create modal first + const modalPart = await parts.createModalEditorPart(); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + await modalPart.activeGroup.openEditor(input, { pinned: true }); + + // findGroup without MODAL_GROUP should NOT close the modal + const newInput = createTestFileEditorInput(URI.file('foo/baz'), TEST_EDITOR_INPUT_ID); + const result = instantiationService.invokeFunction(accessor => findGroup(accessor, { resource: newInput.resource }, undefined)); + + // Since the setting is 'on', modal stays open + const [group] = result instanceof Promise ? await result : result; + + assert.ok(parts.activeModalEditorPart); + assert.strictEqual(group.id, modalPart.activeGroup.id); + + modalPart.close(); + }); + + test('findGroup auto-closes modal when setting is not on', async () => { + const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench.editor.useModal', 'off'); + instantiationService.stub(IConfigurationService, configurationService); + const parts = await createEditorParts(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, parts); + + // Create modal + const modalPart = await parts.createModalEditorPart(); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + await modalPart.activeGroup.openEditor(input, { pinned: true }); + + assert.ok(parts.activeModalEditorPart); + + // findGroup without MODAL_GROUP should close the modal + const newInput = createTestFileEditorInput(URI.file('foo/baz'), TEST_EDITOR_INPUT_ID); + instantiationService.invokeFunction(accessor => findGroup(accessor, { resource: newInput.resource }, undefined)); + + assert.strictEqual(parts.activeModalEditorPart, undefined); + }); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index c1c1f94f062bd..a1ca22b3c4a2d 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -372,7 +372,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } private getEditorGroupFromOptions(isTextual: boolean, options: { groupId?: number; openToSide?: boolean }): PreferredGroup { - if (!isTextual && this.configurationService.getValue('workbench.editor.allowOpenInModalEditor')) { + if (!isTextual && this.configurationService.getValue('workbench.editor.useModal') !== 'off') { return MODAL_GROUP; } if (options.openToSide) { diff --git a/build/vite/fixtures/aiStats.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/aiStats.fixture.ts similarity index 89% rename from build/vite/fixtures/aiStats.fixture.ts rename to src/vs/workbench/test/browser/componentFixtures/aiStats.fixture.ts index 5ebe414b52b62..7df2b70d7d598 100644 --- a/build/vite/fixtures/aiStats.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/aiStats.fixture.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { observableValue } from '../../../src/vs/base/common/observable'; -import { createAiStatsHover, IAiStatsHoverData } from '../../../src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar'; -import { ISessionData } from '../../../src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsChart'; -import { Random } from '../../../src/vs/editor/test/common/core/random'; -import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils'; +import { observableValue } from '../../../../base/common/observable.js'; +import { createAiStatsHover, IAiStatsHoverData } from '../../../contrib/editTelemetry/browser/editStats/aiStatsStatusBar.js'; +import { ISessionData } from '../../../contrib/editTelemetry/browser/editStats/aiStatsChart.js'; +import { Random } from '../../../../editor/test/common/core/random.js'; +import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils.js'; export default defineThemedFixtureGroup({ AiStatsHover: defineComponentFixture({ diff --git a/build/vite/fixtures/baseUI.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/baseUI.fixture.ts similarity index 92% rename from build/vite/fixtures/baseUI.fixture.ts rename to src/vs/workbench/test/browser/componentFixtures/baseUI.fixture.ts index 4bcf0be523519..19579a21ab1a7 100644 --- a/build/vite/fixtures/baseUI.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/baseUI.fixture.ts @@ -3,21 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $ } from '../../../src/vs/base/browser/dom'; -import { Codicon } from '../../../src/vs/base/common/codicons'; -import { ThemeIcon } from '../../../src/vs/base/common/themables'; -import { Action, Separator } from '../../../src/vs/base/common/actions'; +import { $ } from '../../../../base/browser/dom.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Action, Separator } from '../../../../base/common/actions.js'; // UI Components -import { Button, ButtonBar, ButtonWithDescription, unthemedButtonStyles } from '../../../src/vs/base/browser/ui/button/button'; -import { Toggle, Checkbox, unthemedToggleStyles } from '../../../src/vs/base/browser/ui/toggle/toggle'; -import { InputBox, MessageType, unthemedInboxStyles } from '../../../src/vs/base/browser/ui/inputbox/inputBox'; -import { CountBadge } from '../../../src/vs/base/browser/ui/countBadge/countBadge'; -import { ActionBar } from '../../../src/vs/base/browser/ui/actionbar/actionbar'; -import { ProgressBar } from '../../../src/vs/base/browser/ui/progressbar/progressbar'; -import { HighlightedLabel } from '../../../src/vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { Button, ButtonBar, ButtonWithDescription, unthemedButtonStyles } from '../../../../base/browser/ui/button/button.js'; +import { Toggle, Checkbox, unthemedToggleStyles } from '../../../../base/browser/ui/toggle/toggle.js'; +import { InputBox, MessageType, unthemedInboxStyles } from '../../../../base/browser/ui/inputbox/inputBox.js'; +import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js'; +import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js'; +import { HighlightedLabel } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; -import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils'; +import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils.js'; + + +export default defineThemedFixtureGroup({ + Buttons: defineComponentFixture({ + render: renderButtons, + }), + + ButtonBar: defineComponentFixture({ + render: renderButtonBar, + }), + + Toggles: defineComponentFixture({ + render: renderToggles, + }), + + InputBoxes: defineComponentFixture({ + render: renderInputBoxes, + }), + + CountBadges: defineComponentFixture({ + render: renderCountBadges, + }), + + ActionBar: defineComponentFixture({ + render: renderActionBar, + }), + + ProgressBars: defineComponentFixture({ + render: renderProgressBars, + }), + + HighlightedLabels: defineComponentFixture({ + render: renderHighlightedLabels, + }), +}); // ============================================================================ @@ -247,12 +282,6 @@ function renderInputBoxes({ container, disposableStore }: ComponentFixtureContex container.style.gap = '16px'; container.style.width = '350px'; - // Normal input - const normalInput = disposableStore.add(new InputBox(container, undefined, { - placeholder: 'Enter search query...', - inputBoxStyles: themedInputBoxStyles, - })); - // Input with value const filledInput = disposableStore.add(new InputBox(container, undefined, { placeholder: 'File path', @@ -418,11 +447,6 @@ function renderProgressBars({ container, disposableStore }: ComponentFixtureCont return barContainer; }; - // Infinite progress - const infiniteSection = createSection('Infinite Progress (loading...)'); - const infiniteBar = disposableStore.add(new ProgressBar(infiniteSection, themedProgressBarOptions)); - infiniteBar.infinite(); - // Discrete progress - 30% const progress30Section = createSection('Discrete Progress - 30%'); const progress30Bar = disposableStore.add(new ProgressBar(progress30Section, themedProgressBarOptions)); @@ -490,42 +514,3 @@ function renderHighlightedLabels({ container }: ComponentFixtureContext): HTMLEl return container; } - - -// ============================================================================ -// Export Fixtures -// ============================================================================ - -export default defineThemedFixtureGroup({ - Buttons: defineComponentFixture({ - render: renderButtons, - }), - - ButtonBar: defineComponentFixture({ - render: renderButtonBar, - }), - - Toggles: defineComponentFixture({ - render: renderToggles, - }), - - InputBoxes: defineComponentFixture({ - render: renderInputBoxes, - }), - - CountBadges: defineComponentFixture({ - render: renderCountBadges, - }), - - ActionBar: defineComponentFixture({ - render: renderActionBar, - }), - - ProgressBars: defineComponentFixture({ - render: renderProgressBars, - }), - - HighlightedLabels: defineComponentFixture({ - render: renderHighlightedLabels, - }), -}); diff --git a/build/vite/fixtures/editor/codeEditor.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/codeEditor.fixture.ts similarity index 84% rename from build/vite/fixtures/editor/codeEditor.fixture.ts rename to src/vs/workbench/test/browser/componentFixtures/codeEditor.fixture.ts index bdaaf35bf0fcf..af7ff83463719 100644 --- a/build/vite/fixtures/editor/codeEditor.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/codeEditor.fixture.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from '../../../../src/vs/base/common/uri'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../src/vs/editor/browser/widget/codeEditor/codeEditorWidget'; -import { ComponentFixtureContext, createEditorServices, createTextModel, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils'; +import { URI } from '../../../../base/common/uri.js'; +import { ComponentFixtureContext, createEditorServices, defineThemedFixtureGroup, defineComponentFixture, createTextModel } from './fixtureUtils.js'; +import { ICodeEditorWidgetOptions, CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; + const SAMPLE_CODE = `// Welcome to VS Code function greet(name: string): string { diff --git a/build/vite/fixtures/fixtureUtils.ts b/src/vs/workbench/test/browser/componentFixtures/fixtureUtils.ts similarity index 66% rename from build/vite/fixtures/fixtureUtils.ts rename to src/vs/workbench/test/browser/componentFixtures/fixtureUtils.ts index fd90169dae792..0aa4580051806 100644 --- a/build/vite/fixtures/fixtureUtils.ts +++ b/src/vs/workbench/test/browser/componentFixtures/fixtureUtils.ts @@ -3,98 +3,98 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// This should be the only place that is allowed to import from @vscode/component-explorer +// eslint-disable-next-line local/code-import-patterns import { defineFixture, defineFixtureGroup, defineFixtureVariants } from '@vscode/component-explorer'; -import { DisposableStore, toDisposable } from '../../../src/vs/base/common/lifecycle'; -import { URI } from '../../../src/vs/base/common/uri'; -import '../style.css'; +import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +// eslint-disable-next-line local/code-import-patterns +import '../../../../../../build/vite/style.css'; // Theme -import { COLOR_THEME_DARK_INITIAL_COLORS, COLOR_THEME_LIGHT_INITIAL_COLORS } from '../../../src/vs/workbench/services/themes/common/workbenchThemeService'; -import { ColorThemeData } from '../../../src/vs/workbench/services/themes/common/colorThemeData'; -import { ColorScheme } from '../../../src/vs/platform/theme/common/theme'; -import { generateColorThemeCSS } from '../../../src/vs/workbench/services/themes/browser/colorThemeCss'; -import { Registry } from '../../../src/vs/platform/registry/common/platform'; -import { Extensions as ThemingExtensions, IThemingRegistry } from '../../../src/vs/platform/theme/common/themeService'; -import { IEnvironmentService } from '../../../src/vs/platform/environment/common/environment'; -import { getIconsStyleSheet } from '../../../src/vs/platform/theme/browser/iconsStyleSheet'; +import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { getIconsStyleSheet } from '../../../../platform/theme/browser/iconsStyleSheet.js'; +import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { IColorTheme, IThemeService, IThemingRegistry, Extensions as ThemingExtensions } from '../../../../platform/theme/common/themeService.js'; +import { generateColorThemeCSS } from '../../../services/themes/browser/colorThemeCss.js'; +import { ColorThemeData } from '../../../services/themes/common/colorThemeData.js'; +import { COLOR_THEME_DARK_INITIAL_COLORS, COLOR_THEME_LIGHT_INITIAL_COLORS } from '../../../services/themes/common/workbenchThemeService.js'; // Instantiation -import { ServiceCollection } from '../../../src/vs/platform/instantiation/common/serviceCollection'; -import { SyncDescriptor } from '../../../src/vs/platform/instantiation/common/descriptors'; -import { ServiceIdentifier } from '../../../src/vs/platform/instantiation/common/instantiation'; -import { TestInstantiationService } from '../../../src/vs/platform/instantiation/test/common/instantiationServiceMock'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import { ServiceIdentifier } from '../../../../platform/instantiation/common/instantiation.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js'; // Test service implementations -import { TestAccessibilityService } from '../../../src/vs/platform/accessibility/test/common/testAccessibilityService'; -import { MockKeybindingService, MockContextKeyService } from '../../../src/vs/platform/keybinding/test/common/mockKeybindingService'; -import { TestClipboardService } from '../../../src/vs/platform/clipboard/test/common/testClipboardService'; -import { TestEditorWorkerService } from '../../../src/vs/editor/test/common/services/testEditorWorkerService'; -import { NullOpenerService } from '../../../src/vs/platform/opener/test/common/nullOpenerService'; -import { TestNotificationService } from '../../../src/vs/platform/notification/test/common/testNotificationService'; -import { TestDialogService } from '../../../src/vs/platform/dialogs/test/common/testDialogService'; -import { TestConfigurationService } from '../../../src/vs/platform/configuration/test/common/testConfigurationService'; -import { TestTextResourcePropertiesService } from '../../../src/vs/editor/test/common/services/testTextResourcePropertiesService'; -import { TestThemeService } from '../../../src/vs/platform/theme/test/common/testThemeService'; -import { TestLanguageConfigurationService } from '../../../src/vs/editor/test/common/modes/testLanguageConfigurationService'; -import { TestCodeEditorService, TestCommandService } from '../../../src/vs/editor/test/browser/editorTestServices'; -import { TestTreeSitterLibraryService } from '../../../src/vs/editor/test/common/services/testTreeSitterLibraryService'; -import { TestMenuService } from '../../../src/vs/workbench/test/browser/workbenchTestServices'; - -// Service interfaces -import { IAccessibilityService } from '../../../src/vs/platform/accessibility/common/accessibility'; -import { IKeybindingService } from '../../../src/vs/platform/keybinding/common/keybinding'; -import { IClipboardService } from '../../../src/vs/platform/clipboard/common/clipboardService'; -import { IEditorWorkerService } from '../../../src/vs/editor/common/services/editorWorker'; -import { IOpenerService } from '../../../src/vs/platform/opener/common/opener'; -import { INotificationService } from '../../../src/vs/platform/notification/common/notification'; -import { IDialogService } from '../../../src/vs/platform/dialogs/common/dialogs'; -import { IUndoRedoService } from '../../../src/vs/platform/undoRedo/common/undoRedo'; -import { UndoRedoService } from '../../../src/vs/platform/undoRedo/common/undoRedoService'; -import { ILanguageService } from '../../../src/vs/editor/common/languages/language'; -import { LanguageService } from '../../../src/vs/editor/common/services/languageService'; -import { ILanguageConfigurationService } from '../../../src/vs/editor/common/languages/languageConfigurationRegistry'; -import { IConfigurationService } from '../../../src/vs/platform/configuration/common/configuration'; -import { ITextResourcePropertiesService } from '../../../src/vs/editor/common/services/textResourceConfiguration'; -import { IColorTheme, IThemeService } from '../../../src/vs/platform/theme/common/themeService'; -import { ILogService, NullLogService, ILoggerService, NullLoggerService } from '../../../src/vs/platform/log/common/log'; -import { IModelService } from '../../../src/vs/editor/common/services/model'; -import { ModelService } from '../../../src/vs/editor/common/services/modelService'; -import { ICodeEditorService } from '../../../src/vs/editor/browser/services/codeEditorService'; -import { IContextKeyService } from '../../../src/vs/platform/contextkey/common/contextkey'; -import { ICommandService } from '../../../src/vs/platform/commands/common/commands'; -import { ITelemetryService } from '../../../src/vs/platform/telemetry/common/telemetry'; -import { NullTelemetryServiceShape } from '../../../src/vs/platform/telemetry/common/telemetryUtils'; -import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../src/vs/editor/common/services/languageFeatureDebounce'; -import { ILanguageFeaturesService } from '../../../src/vs/editor/common/services/languageFeatures'; -import { LanguageFeaturesService } from '../../../src/vs/editor/common/services/languageFeaturesService'; -import { ITreeSitterLibraryService } from '../../../src/vs/editor/common/services/treeSitter/treeSitterLibraryService'; -import { IInlineCompletionsService, InlineCompletionsService } from '../../../src/vs/editor/browser/services/inlineCompletionsService'; -import { ICodeLensCache } from '../../../src/vs/editor/contrib/codelens/browser/codeLensCache'; -import { IHoverService } from '../../../src/vs/platform/hover/browser/hover'; -import { IDataChannelService, NullDataChannelService } from '../../../src/vs/platform/dataChannel/common/dataChannel'; -import { IContextMenuService, IContextViewService } from '../../../src/vs/platform/contextview/browser/contextView'; -import { ILabelService } from '../../../src/vs/platform/label/common/label'; -import { IMenuService } from '../../../src/vs/platform/actions/common/actions'; -import { IActionViewItemService, NullActionViewItemService } from '../../../src/vs/platform/actions/browser/actionViewItemService'; -import { IDefaultAccountService } from '../../../src/vs/platform/defaultAccount/common/defaultAccount'; -import { IStorageService, IStorageValueChangeEvent, IWillSaveStateEvent, StorageScope, StorageTarget, IStorageTargetChangeEvent, IStorageEntry, WillSaveStateReason, IWorkspaceStorageValueChangeEvent, IProfileStorageValueChangeEvent, IApplicationStorageValueChangeEvent } from '../../../src/vs/platform/storage/common/storage'; -import { Emitter, Event } from '../../../src/vs/base/common/event'; -import { mock } from '../../../src/vs/base/test/common/mock'; -import { IAnyWorkspaceIdentifier } from '../../../src/vs/platform/workspace/common/workspace'; -import { IUserDataProfile } from '../../../src/vs/platform/userDataProfile/common/userDataProfile'; -import { IUserInteractionService, MockUserInteractionService } from '../../../src/vs/platform/userInteraction/browser/userInteractionService'; +import { TestCodeEditorService, TestCommandService } from '../../../../editor/test/browser/editorTestServices.js'; +import { TestLanguageConfigurationService } from '../../../../editor/test/common/modes/testLanguageConfigurationService.js'; +import { TestEditorWorkerService } from '../../../../editor/test/common/services/testEditorWorkerService.js'; +import { TestTextResourcePropertiesService } from '../../../../editor/test/common/services/testTextResourcePropertiesService.js'; +import { TestTreeSitterLibraryService } from '../../../../editor/test/common/services/testTreeSitterLibraryService.js'; +import { TestAccessibilityService } from '../../../../platform/accessibility/test/common/testAccessibilityService.js'; +import { TestClipboardService } from '../../../../platform/clipboard/test/common/testClipboardService.js'; +import { TestConfigurationService } from '../../../../platform/configuration/test/common/testConfigurationService.js'; +import { TestDialogService } from '../../../../platform/dialogs/test/common/testDialogService.js'; +import { MockContextKeyService, MockKeybindingService } from '../../../../platform/keybinding/test/common/mockKeybindingService.js'; +import { TestNotificationService } from '../../../../platform/notification/test/common/testNotificationService.js'; +import { NullOpenerService } from '../../../../platform/opener/test/common/nullOpenerService.js'; +import { TestThemeService } from '../../../../platform/theme/test/common/testThemeService.js'; +import { TestMenuService } from '../workbenchTestServices.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { mock } from '../../../../base/test/common/mock.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { IInlineCompletionsService, InlineCompletionsService } from '../../../../editor/browser/services/inlineCompletionsService.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { ILanguageConfigurationService } from '../../../../editor/common/languages/languageConfigurationRegistry.js'; +import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js'; +import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../../editor/common/services/languageFeatureDebounce.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { LanguageFeaturesService } from '../../../../editor/common/services/languageFeaturesService.js'; +import { LanguageService } from '../../../../editor/common/services/languageService.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { ModelService } from '../../../../editor/common/services/modelService.js'; +import { ITextResourcePropertiesService } from '../../../../editor/common/services/textResourceConfiguration.js'; +import { ITreeSitterLibraryService } from '../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; +import { ICodeLensCache } from '../../../../editor/contrib/codelens/browser/codeLensCache.js'; +import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; +import { IActionViewItemService, NullActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js'; +import { IMenuService } from '../../../../platform/actions/common/actions.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; +import { IDataChannelService, NullDataChannelService } from '../../../../platform/dataChannel/common/dataChannel.js'; +import { IDefaultAccountService } from '../../../../platform/defaultAccount/common/defaultAccount.js'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { ILoggerService, ILogService, NullLoggerService, NullLogService } from '../../../../platform/log/common/log.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IApplicationStorageValueChangeEvent, IProfileStorageValueChangeEvent, IStorageEntry, IStorageService, IStorageTargetChangeEvent, IStorageValueChangeEvent, IWillSaveStateEvent, IWorkspaceStorageValueChangeEvent, StorageScope, StorageTarget, WillSaveStateReason } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { NullTelemetryServiceShape } from '../../../../platform/telemetry/common/telemetryUtils.js'; +import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js'; +import { UndoRedoService } from '../../../../platform/undoRedo/common/undoRedoService.js'; +import { IUserDataProfile } from '../../../../platform/userDataProfile/common/userDataProfile.js'; +import { IUserInteractionService, MockUserInteractionService } from '../../../../platform/userInteraction/browser/userInteractionService.js'; +import { IAnyWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js'; // Editor -import { ITextModel } from '../../../src/vs/editor/common/model'; +import { ITextModel } from '../../../../editor/common/model.js'; // Import color registrations to ensure colors are available -import '../../../src/vs/platform/theme/common/colors/baseColors'; -import '../../../src/vs/platform/theme/common/colors/editorColors'; -import '../../../src/vs/platform/theme/common/colors/listColors'; -import '../../../src/vs/platform/theme/common/colors/miscColors'; -import '../../../src/vs/workbench/common/theme'; +import '../../../../platform/theme/common/colors/baseColors.js'; +import '../../../../platform/theme/common/colors/editorColors.js'; +import '../../../../platform/theme/common/colors/listColors.js'; +import '../../../../platform/theme/common/colors/miscColors.js'; +import '../../../common/theme.js'; /** * A storage service that never stores anything and always returns the default/fallback value. @@ -363,7 +363,7 @@ export function createEditorServices(disposables: DisposableStore, options?: Cre put: () => { }, get: () => undefined, delete: () => { }, - } as ICodeLensCache); + }); defineInstance(IHoverService, { _serviceBrand: undefined, showDelayedHover: () => undefined, @@ -374,7 +374,7 @@ export function createEditorServices(disposables: DisposableStore, options?: Cre showAndFocusLastHover: () => { }, setupManagedHover: () => ({ dispose: () => { }, show: () => { }, hide: () => { }, update: () => { } }), showManagedHover: () => { }, - } as IHoverService); + }); defineInstance(IDefaultAccountService, { _serviceBrand: undefined, onDidChangeDefaultAccount: new Emitter().event, @@ -385,7 +385,8 @@ export function createEditorServices(disposables: DisposableStore, options?: Cre setDefaultAccountProvider: () => { }, refresh: async () => null, signIn: async () => null, - } as IDefaultAccountService); + signOut: async () => { }, + }); // User interaction service with focus simulation enabled (all elements appear focused in fixtures) defineInstance(IUserInteractionService, new MockUserInteractionService(true, false)); @@ -397,8 +398,7 @@ export function createEditorServices(disposables: DisposableStore, options?: Cre disposables.add(toDisposable(() => { for (const id of serviceIdentifiers) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const instanceOrDescriptor = services.get(id) as any; + const instanceOrDescriptor = services.get(id); if (typeof instanceOrDescriptor?.dispose === 'function') { instanceOrDescriptor.dispose(); } @@ -417,14 +417,17 @@ export function registerWorkbenchServices(registration: ServiceRegistration): vo showContextMenu: () => { }, onDidShowContextMenu: () => ({ dispose: () => { } }), onDidHideContextMenu: () => ({ dispose: () => { } }), - } as unknown as IContextMenuService); + _serviceBrand: undefined, + }); registration.defineInstance(IContextViewService, { - showContextView: () => ({ dispose: () => { } }), + showContextView: () => ({ close: () => { } }), hideContextView: () => { }, - getContextViewElement: () => null, + getContextViewElement: () => { throw new Error('Not implemented'); }, layout: () => { }, - } as unknown as IContextViewService); + anchorAlignment: 0, + _serviceBrand: undefined, + }); registration.defineInstance(ILabelService, { getUriLabel: (uri: URI) => uri.path, @@ -435,7 +438,9 @@ export function registerWorkbenchServices(registration: ServiceRegistration): vo registerFormatter: () => ({ dispose: () => { } }), onDidChangeFormatters: () => ({ dispose: () => { } }), registerCachedFormatter: () => ({ dispose: () => { } }), - } as unknown as ILabelService); + _serviceBrand: undefined, + getHostTooltip: () => '', + }); registration.define(IMenuService, TestMenuService); registration.define(IActionViewItemService, NullActionViewItemService); @@ -488,10 +493,11 @@ export function defineComponentFixture(options: ComponentFixtureOptions): Themed displayMode: { type: 'component' }, properties: [], background: theme === darkTheme ? 'dark' : 'light', - render: async (container: HTMLElement) => { + render: (container: HTMLElement) => { const disposableStore = new DisposableStore(); setupTheme(container, theme); - return options.render({ container, disposableStore, theme }); + options.render({ container, disposableStore, theme }); + return disposableStore; }, }); diff --git a/build/vite/fixtures/editor/inlineCompletions.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/inlineCompletions.fixture.ts similarity index 74% rename from build/vite/fixtures/editor/inlineCompletions.fixture.ts rename to src/vs/workbench/test/browser/componentFixtures/inlineCompletions.fixture.ts index 06abdece6b686..3f30440013af6 100644 --- a/build/vite/fixtures/editor/inlineCompletions.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/inlineCompletions.fixture.ts @@ -3,20 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { constObservable } from '../../../../src/vs/base/common/observable'; -import { URI } from '../../../../src/vs/base/common/uri'; -import { Range } from '../../../../src/vs/editor/common/core/range'; -import { IEditorOptions } from '../../../../src/vs/editor/common/config/editorOptions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../src/vs/editor/browser/widget/codeEditor/codeEditorWidget'; -import { EditorExtensionsRegistry } from '../../../../src/vs/editor/browser/editorExtensions'; -import { InlineCompletionsController } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; -import { InlineCompletionsSource, InlineCompletionsState } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource'; -import { InlineEditItem } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem'; -import { TextModelValueReference } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/textModelValueReference'; -import { ComponentFixtureContext, createEditorServices, createTextModel, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils'; // Import to register the inline completions contribution -import '../../../../src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution'; +import { constObservable, IObservableWithChange } from '../../../../base/common/observable.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ComponentFixtureContext, createEditorServices, defineThemedFixtureGroup, defineComponentFixture, createTextModel } from './fixtureUtils.js'; +import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; +import { ICodeEditorWidgetOptions, CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; +import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js'; +import '../../../../editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.js'; +import { InlineCompletionsSource, InlineCompletionsState } from '../../../../editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.js'; +import { InlineEditItem } from '../../../../editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.js'; +import { TextModelValueReference } from '../../../../editor/contrib/inlineCompletions/browser/model/textModelValueReference.js'; // ============================================================================ @@ -52,15 +52,13 @@ function renderInlineEdit(options: InlineEditOptions): HTMLElement { instantiationService.stubInstance(InlineCompletionsSource, { cancelUpdate: () => { }, clear: () => { }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - clearOperationOnTextModelChange: constObservable(undefined) as any, + clearOperationOnTextModelChange: constObservable(undefined) as IObservableWithChange, clearSuggestWidgetInlineCompletions: () => { }, dispose: () => { }, fetch: async () => true, inlineCompletions: constObservable(new InlineCompletionsState([ InlineEditItem.createForTest( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - TextModelValueReference.snapshot(textModel as any), + TextModelValueReference.snapshot(textModel), new Range( options.range.startLineNumber, options.range.startColumn, @@ -117,11 +115,11 @@ export default defineThemedFixtureGroup({ render: (context) => renderInlineEdit({ ...context, code: `function greet(name) { - console.log("Hello, " + name); + console.log("Hello, " + name); }`, cursorLine: 2, range: { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 100 }, - newText: ' console.log(`Hello, ${name}!`);', + newText: '\tconsole.log(`Hello, ${name}!`);', }), }), diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png new file mode 100644 index 0000000000000..2d42f424f6b88 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Light.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Light.png new file mode 100644 index 0000000000000..7fb858eae279f Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Dark.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Dark.png new file mode 100644 index 0000000000000..9ebb23a380665 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Light.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Light.png new file mode 100644 index 0000000000000..e631cc0c3c30b Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Dark.png new file mode 100644 index 0000000000000..0ced565eff406 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Light.png new file mode 100644 index 0000000000000..6523771ae702a Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Dark.png new file mode 100644 index 0000000000000..ab1c28ac44ff2 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Light.png new file mode 100644 index 0000000000000..9afb35e184937 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Dark.png new file mode 100644 index 0000000000000..cd2a52efff43b Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Light.png new file mode 100644 index 0000000000000..9651d1f2d57b0 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Dark.png new file mode 100644 index 0000000000000..e6cc74cdfc2c5 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Light.png new file mode 100644 index 0000000000000..8e9cd825d1fd1 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Dark.png new file mode 100644 index 0000000000000..36ed40b5266b7 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Light.png new file mode 100644 index 0000000000000..cfca6a3329a9a Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Dark.png new file mode 100644 index 0000000000000..721a618d708cd Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Light.png new file mode 100644 index 0000000000000..d35879dc97583 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Dark.png new file mode 100644 index 0000000000000..bb25647846e3e Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Light.png new file mode 100644 index 0000000000000..a542ed0e11db3 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Dark.png new file mode 100644 index 0000000000000..a59cc7894d20e Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Light.png new file mode 100644 index 0000000000000..49c44de392fca Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Dark.png b/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Dark.png new file mode 100644 index 0000000000000..b5d9569f906bd Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Light.png b/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Light.png new file mode 100644 index 0000000000000..0a8e19aeb3abf Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Dark.png new file mode 100644 index 0000000000000..b10535a2ffd01 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Light.png new file mode 100644 index 0000000000000..a6fd87499a814 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Dark.png new file mode 100644 index 0000000000000..52a2280560a1d Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Light.png new file mode 100644 index 0000000000000..65169b87a1126 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Dark.png new file mode 100644 index 0000000000000..297cb38eade94 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Light.png new file mode 100644 index 0000000000000..3d564e02878d5 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Light.png differ diff --git a/test/componentFixtures/component-explorer-diff.json b/test/componentFixtures/component-explorer-diff.json new file mode 100644 index 0000000000000..624f67f6ebcb8 --- /dev/null +++ b/test/componentFixtures/component-explorer-diff.json @@ -0,0 +1,30 @@ +{ + "screenshotDir": ".screenshots", + "sessions": [ + { + "name": "current" + }, + { + "name": "baseline", + "source": { + "worktree": { + "ref": "HEAD", + "install": { + "command": "echo 'noop'" + } + } + } + } + ], + "compare": { + "baseline": "baseline", + "current": "current" + }, + "vite": { + "hmr": { + "allowedPaths": [ + "*.css" + ] + } + } +} diff --git a/test/componentFixtures/component-explorer.json b/test/componentFixtures/component-explorer.json new file mode 100644 index 0000000000000..12a3fd30d4234 --- /dev/null +++ b/test/componentFixtures/component-explorer.json @@ -0,0 +1,16 @@ +{ + "screenshotDir": ".screenshots", + "sessions": [ + { + "name": "current" + } + ], + "viteConfig": "../../build/vite/vite.config.ts", + "vite": { + "hmr": { + "allowedPaths": [ + "*.css" + ] + } + } +}