mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-03 07:44:02 +01:00
Merge branch 'main' into rebornix/practical-dingo
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <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 <run-id> --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.
|
||||
@@ -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 - <<EOF
|
||||
{"output":{"title":"$TITLE","summary":"$TITLE"}}
|
||||
EOF
|
||||
fi
|
||||
|
||||
- name: Post summary
|
||||
run: |
|
||||
if [ -f test/componentFixtures/.screenshots/report.md ]; then
|
||||
cat test/componentFixtures/.screenshots/report.md >> $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="<!-- screenshot-report -->"
|
||||
# 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
|
||||
@@ -10,6 +10,7 @@ src/vs/base/browser/ui/codicons/codicon/codicon.ttf
|
||||
/out*/
|
||||
/extensions/**/out/
|
||||
build/node_modules
|
||||
build/darwin/.dmgbuild
|
||||
coverage/
|
||||
test_data/
|
||||
test-results/
|
||||
@@ -25,3 +26,5 @@ product.overrides.json
|
||||
.vscode-test
|
||||
vscode-telemetry-docs/
|
||||
test-output.json
|
||||
test/componentFixtures/.screenshots/*
|
||||
!test/componentFixtures/.screenshots/baseline/
|
||||
|
||||
Vendored
+7
-5
@@ -100,10 +100,10 @@
|
||||
// --- TypeScript ---
|
||||
"typescript.experimental.useTsgo": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.preferences.importModuleSpecifier": "relative",
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"js/ts.preferences.importModuleSpecifier": "relative",
|
||||
"js/ts.preferences.quoteStyle": "single",
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"js/ts.preferences.autoImportFileExcludePatterns": [
|
||||
"@xterm/xterm",
|
||||
"@xterm/headless",
|
||||
"node-pty",
|
||||
@@ -144,6 +144,9 @@
|
||||
"ts": "warning",
|
||||
"eslint": "warning"
|
||||
},
|
||||
"git.worktreeIncludeFiles": [
|
||||
"product.overrides.json"
|
||||
],
|
||||
// --- GitHub ---
|
||||
"githubPullRequests.experimental.createView": true,
|
||||
"githubPullRequests.assignCreated": "${user}",
|
||||
@@ -199,12 +202,11 @@
|
||||
"sash"
|
||||
],
|
||||
// --- Workbench ---
|
||||
// "application.experimental.rendererProfiling": true, // https://github.com/microsoft/vscode/issues/265654
|
||||
"editor.aiStats.enabled": true, // Team selfhosting on ai stats
|
||||
"azureMcp.enabledServices": [
|
||||
"kusto" // Needed for kusto tool in data.prompt.md
|
||||
],
|
||||
"azureMcp.serverMode": "all",
|
||||
"azureMcp.readOnly": true,
|
||||
"debug.breakpointsView.presentation": "tree"
|
||||
"debug.breakpointsView.presentation": "tree",
|
||||
}
|
||||
|
||||
@@ -133,8 +133,6 @@ jobs:
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
# Needed for https://github.com/dmgbuild/dmgbuild/blob/main/src/dmgbuild/badge.py
|
||||
python3 -m pip install pyobjc-framework-Quartz
|
||||
DMG_OUT="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_dmg"
|
||||
mkdir -p $DMG_OUT
|
||||
node build/darwin/create-dmg.ts $(agent.builddirectory) $DMG_OUT
|
||||
|
||||
@@ -240,8 +240,6 @@ steps:
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
# Needed for https://github.com/dmgbuild/dmgbuild/blob/main/src/dmgbuild/badge.py
|
||||
python3 -m pip install pyobjc-framework-Quartz
|
||||
DMG_OUT="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_dmg"
|
||||
mkdir -p $DMG_OUT
|
||||
node build/darwin/create-dmg.ts $(agent.builddirectory) $DMG_OUT
|
||||
|
||||
@@ -172,7 +172,7 @@ extends:
|
||||
enabled: true
|
||||
configFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/tsaoptions.json
|
||||
binskim:
|
||||
analyzeTargetGlob: '+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.exe;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.dll;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.node;-:file|$(Agent.BuildDirectory)/VSCode-*/**/resources/**/*.node;-:file|$(Build.SourcesDirectory)/.build/**/system-setup/VSCodeSetup*.exe;-:file|$(Build.SourcesDirectory)/.build/**/user-setup/VSCodeUserSetup*.exe'
|
||||
analyzeTargetGlob: '+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.exe;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.dll;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.node;-:file|$(Agent.BuildDirectory)/VSCode-*/**/resources/**/*.exe;-:file|$(Agent.BuildDirectory)/VSCode-*/**/resources/**/*.node;-:file|$(Build.SourcesDirectory)/.build/**/system-setup/VSCodeSetup*.exe;-:file|$(Build.SourcesDirectory)/.build/**/user-setup/VSCodeUserSetup*.exe'
|
||||
codeql:
|
||||
runSourceLanguagesInSourceAnalysis: true
|
||||
compiled:
|
||||
|
||||
+129
-56
@@ -10,41 +10,131 @@ import { spawn } from '@malept/cross-spawn-promise';
|
||||
const root = path.dirname(path.dirname(import.meta.dirname));
|
||||
const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8'));
|
||||
|
||||
interface DmgBuildSettings {
|
||||
title: string;
|
||||
icon?: string | null;
|
||||
'badge-icon'?: string | null;
|
||||
background?: string;
|
||||
'background-color'?: string;
|
||||
'icon-size'?: number;
|
||||
'text-size'?: number;
|
||||
format?: string;
|
||||
window?: {
|
||||
position?: { x: number; y: number };
|
||||
size?: { width: number; height: number };
|
||||
};
|
||||
contents: Array<{
|
||||
path: string;
|
||||
x: number;
|
||||
y: number;
|
||||
type: 'file' | 'link';
|
||||
name?: string;
|
||||
}>;
|
||||
const DMGBUILD_REPO = 'https://github.com/dmgbuild/dmgbuild.git';
|
||||
const DMGBUILD_COMMIT = '75c8a6c7835c5b73dfd4510d92a8f357f93a5fbf';
|
||||
const MIN_PYTHON_VERSION = [3, 10];
|
||||
|
||||
function getDmgBuildPath(): string {
|
||||
return path.join(import.meta.dirname, '.dmgbuild');
|
||||
}
|
||||
|
||||
function getDmgBuilderPath(): string {
|
||||
return path.join(import.meta.dirname, '..', 'node_modules', 'dmg-builder');
|
||||
function getVenvPath(): string {
|
||||
return path.join(getDmgBuildPath(), 'venv');
|
||||
}
|
||||
|
||||
function getDmgBuilderVendorPath(): string {
|
||||
return path.join(getDmgBuilderPath(), 'vendor');
|
||||
function getPythonPath(): string {
|
||||
return path.join(getVenvPath(), 'bin', 'python3');
|
||||
}
|
||||
|
||||
async function checkPythonVersion(pythonBin: string): Promise<boolean> {
|
||||
try {
|
||||
const output = await spawn(pythonBin, ['--version']);
|
||||
const match = output.match(/Python (\d+)\.(\d+)/);
|
||||
if (match) {
|
||||
const major = parseInt(match[1], 10);
|
||||
const minor = parseInt(match[2], 10);
|
||||
return major > MIN_PYTHON_VERSION[0] || (major === MIN_PYTHON_VERSION[0] && minor >= MIN_PYTHON_VERSION[1]);
|
||||
}
|
||||
} catch {
|
||||
// not available
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a Python binary that meets the minimum version requirement.
|
||||
* Tries well-known candidates first, and if none are suitable,
|
||||
* installs Python 3.12 via Homebrew.
|
||||
*/
|
||||
async function findSuitablePython(): Promise<string> {
|
||||
const candidates = [
|
||||
'python3',
|
||||
'python3.12',
|
||||
'python3.11',
|
||||
'python3.10',
|
||||
// Homebrew paths (Apple Silicon)
|
||||
'/opt/homebrew/opt/python@3.12/bin/python3',
|
||||
'/opt/homebrew/opt/python@3.11/bin/python3',
|
||||
'/opt/homebrew/opt/python@3.10/bin/python3',
|
||||
// Homebrew paths (Intel)
|
||||
'/usr/local/opt/python@3.12/bin/python3',
|
||||
'/usr/local/opt/python@3.11/bin/python3',
|
||||
'/usr/local/opt/python@3.10/bin/python3',
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (await checkPythonVersion(candidate)) {
|
||||
console.log(`Found suitable Python: ${candidate}`);
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`No Python >= ${MIN_PYTHON_VERSION[0]}.${MIN_PYTHON_VERSION[1]} found, installing via Homebrew...`);
|
||||
await spawn('brew', ['install', 'python@3.12'], {
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: '1', HOMEBREW_NO_INSTALL_CLEANUP: '1' }
|
||||
});
|
||||
|
||||
// Use `brew --prefix` to reliably locate the installation
|
||||
const brewPrefix = (await spawn('brew', ['--prefix', 'python@3.12'])).trim();
|
||||
const brewBinDir = path.join(brewPrefix, 'bin');
|
||||
console.log(`Homebrew Python prefix: ${brewPrefix}`);
|
||||
|
||||
// Try both python3 and python3.12 (keg-only formulae may only have the versioned name)
|
||||
for (const name of ['python3', 'python3.12']) {
|
||||
const fullPath = path.join(brewBinDir, name);
|
||||
if (await checkPythonVersion(fullPath)) {
|
||||
console.log(`Using Homebrew Python: ${fullPath}`);
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Could not find Python >= ${MIN_PYTHON_VERSION[0]}.${MIN_PYTHON_VERSION[1]} even after Homebrew install at ${brewPrefix}.`);
|
||||
}
|
||||
|
||||
async function ensureDmgBuild(): Promise<void> {
|
||||
const dmgBuildPath = getDmgBuildPath();
|
||||
const venvPath = getVenvPath();
|
||||
const markerFile = path.join(dmgBuildPath, '.installed');
|
||||
if (fs.existsSync(markerFile)) {
|
||||
console.log('dmgbuild already installed, skipping setup');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Setting up dmgbuild from GitHub...');
|
||||
if (fs.existsSync(dmgBuildPath)) {
|
||||
fs.rmSync(dmgBuildPath, { recursive: true });
|
||||
}
|
||||
|
||||
console.log(`Cloning dmgbuild from ${DMGBUILD_REPO} at ${DMGBUILD_COMMIT}...`);
|
||||
await spawn('git', ['clone', DMGBUILD_REPO, dmgBuildPath], {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
await spawn('git', ['-C', dmgBuildPath, 'checkout', DMGBUILD_COMMIT], {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
const pythonBin = await findSuitablePython();
|
||||
console.log('Creating Python virtual environment...');
|
||||
await spawn(pythonBin, ['-m', 'venv', venvPath], {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
console.log('Installing dmgbuild and dependencies into venv...');
|
||||
const pipPath = path.join(venvPath, 'bin', 'pip');
|
||||
await spawn(pipPath, ['install', dmgBuildPath], {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
fs.writeFileSync(markerFile, `Installed at ${new Date().toISOString()}\nCommit: ${DMGBUILD_COMMIT}\n`);
|
||||
console.log('dmgbuild setup complete');
|
||||
}
|
||||
|
||||
async function runDmgBuild(settingsFile: string, volumeName: string, artifactPath: string): Promise<void> {
|
||||
const vendorDir = getDmgBuilderVendorPath();
|
||||
const scriptPath = path.join(vendorDir, 'run_dmgbuild.py');
|
||||
await spawn('python3', [scriptPath, '-s', settingsFile, volumeName, artifactPath], {
|
||||
cwd: vendorDir,
|
||||
await ensureDmgBuild();
|
||||
|
||||
const pythonPath = getPythonPath();
|
||||
await spawn(pythonPath, ['-m', 'dmgbuild', '-s', settingsFile, volumeName, artifactPath], {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
}
|
||||
@@ -98,34 +188,17 @@ async function main(buildDir?: string, outDir?: string): Promise<void> {
|
||||
fs.unlinkSync(artifactPath);
|
||||
}
|
||||
|
||||
const settings: DmgBuildSettings = {
|
||||
title,
|
||||
'badge-icon': diskIconPath,
|
||||
background: backgroundPath,
|
||||
format: 'ULMO',
|
||||
'text-size': 12,
|
||||
window: {
|
||||
position: { x: 100, y: 400 },
|
||||
size: { width: 480, height: 352 }
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
path: appPath,
|
||||
x: 120,
|
||||
y: 160,
|
||||
type: 'file'
|
||||
},
|
||||
{
|
||||
path: '/Applications',
|
||||
x: 360,
|
||||
y: 160,
|
||||
type: 'link'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const settingsFile = path.join(outDir, '.dmg-settings.json');
|
||||
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
||||
// Copy and process the settings template for dmgbuild
|
||||
const settingsTemplatePath = path.join(import.meta.dirname, 'dmg-settings.py.template');
|
||||
const settingsFile = path.join(outDir, '.dmg-settings.py');
|
||||
let settingsContent = fs.readFileSync(settingsTemplatePath, 'utf8');
|
||||
settingsContent = settingsContent
|
||||
.replace('{{VOLUME_NAME}}', JSON.stringify(title))
|
||||
.replace('{{BADGE_ICON}}', JSON.stringify(diskIconPath))
|
||||
.replace('{{BACKGROUND}}', JSON.stringify(backgroundPath))
|
||||
.replace('{{APP_PATH}}', JSON.stringify(appPath))
|
||||
.replace('{{APP_NAME}}', JSON.stringify(product.nameLong + '.app'));
|
||||
fs.writeFileSync(settingsFile, settingsContent);
|
||||
|
||||
try {
|
||||
await runDmgBuild(settingsFile, dmgName, artifactPath);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# dmgbuild settings template
|
||||
# Placeholders are replaced at build time
|
||||
|
||||
volume_name = {{VOLUME_NAME}}
|
||||
format = 'ULMO'
|
||||
badge_icon = {{BADGE_ICON}}
|
||||
background = {{BACKGROUND}}
|
||||
|
||||
# Volume size (None = auto-calculate)
|
||||
size = None
|
||||
|
||||
# Files and symlinks
|
||||
files = [{{APP_PATH}}]
|
||||
symlinks = {
|
||||
'Applications': '/Applications'
|
||||
}
|
||||
|
||||
# Window settings
|
||||
show_status_bar = False
|
||||
show_tab_view = False
|
||||
show_toolbar = False
|
||||
show_pathbar = False
|
||||
show_sidebar = False
|
||||
sidebar_width = 180
|
||||
|
||||
# Window position and size
|
||||
window_rect = ((100, 400), (480, 352))
|
||||
|
||||
# Icon view settings
|
||||
default_view = 'icon-view'
|
||||
icon_locations = {
|
||||
{{APP_NAME}}: (120, 160),
|
||||
'Applications': (360, 160)
|
||||
}
|
||||
|
||||
# Text size for icon labels
|
||||
text_size = 12
|
||||
icon_size = 80
|
||||
@@ -389,7 +389,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d
|
||||
|
||||
const isInsiderOrExploration = quality === 'insider' || quality === 'exploration';
|
||||
const embedded = isInsiderOrExploration
|
||||
? (product as typeof product & { embedded?: { nameShort: string; nameLong: string; applicationName: string; dataFolderName: string; darwinBundleIdentifier: string } }).embedded
|
||||
? (product as typeof product & { embedded?: { nameShort: string; nameLong: string; applicationName: string; dataFolderName: string; darwinBundleIdentifier: string; urlProtocol: string } }).embedded
|
||||
: undefined;
|
||||
|
||||
const packageSubJsonStream = isInsiderOrExploration
|
||||
@@ -409,6 +409,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d
|
||||
json.applicationName = embedded.applicationName;
|
||||
json.dataFolderName = embedded.dataFolderName;
|
||||
json.darwinBundleIdentifier = embedded.darwinBundleIdentifier;
|
||||
json.urlProtocol = embedded.urlProtocol;
|
||||
return json;
|
||||
}))
|
||||
.pipe(rename('product.sub.json'))
|
||||
|
||||
Generated
+6
-2979
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,6 @@
|
||||
"ansi-colors": "^3.2.3",
|
||||
"byline": "^5.0.0",
|
||||
"debug": "^4.3.2",
|
||||
"dmg-builder": "26.8.1",
|
||||
"esbuild": "0.27.2",
|
||||
"extract-zip": "^2.0.1",
|
||||
"gulp-merge-json": "^2.1.1",
|
||||
|
||||
Generated
+14
-866
@@ -8,63 +8,13 @@
|
||||
"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",
|
||||
@@ -85,210 +35,6 @@
|
||||
"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",
|
||||
@@ -313,351 +59,6 @@
|
||||
"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"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"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==",
|
||||
"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
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
src/**
|
||||
tsconfig*.json
|
||||
out/**
|
||||
extension.webpack.config.js
|
||||
esbuild*.mts
|
||||
package-lock.json
|
||||
|
||||
+12
-10
@@ -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);
|
||||
@@ -1,6 +1,6 @@
|
||||
src/**
|
||||
tsconfig*.json
|
||||
out/**
|
||||
extension.webpack.config.js
|
||||
esbuild*.mts
|
||||
package-lock.json
|
||||
.vscode
|
||||
|
||||
+12
-10
@@ -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);
|
||||
@@ -1,7 +1,6 @@
|
||||
src/**
|
||||
build/**
|
||||
cgmanifest.json
|
||||
extension.webpack.config.js
|
||||
extension-browser.webpack.config.js
|
||||
esbuild*.mts
|
||||
tsconfig*.json
|
||||
|
||||
|
||||
@@ -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);
|
||||
+12
-10
@@ -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);
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {},
|
||||
"exclude": [
|
||||
"./src/test/**"
|
||||
]
|
||||
}
|
||||
@@ -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.*
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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];
|
||||
@@ -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
|
||||
]
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {},
|
||||
"exclude": [
|
||||
"./src/test/**"
|
||||
],
|
||||
"files": [
|
||||
"./src/ipynbMain.browser.ts",
|
||||
"./src/notebookSerializerWorker.web.ts"
|
||||
]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
out/**
|
||||
extension.webpack.config.js
|
||||
esbuild*.mts
|
||||
package-lock.json
|
||||
src/**
|
||||
.gitignore
|
||||
|
||||
@@ -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<void> {
|
||||
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);
|
||||
@@ -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
|
||||
});
|
||||
Generated
+13
-24
@@ -12,7 +12,7 @@
|
||||
"find-up": "^5.0.0",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"minimatch": "^10.2.1",
|
||||
"minimatch": "^5.1.6",
|
||||
"request-light": "^0.7.0",
|
||||
"vscode-uri": "^3.0.8",
|
||||
"which": "^4.0.0",
|
||||
@@ -58,24 +58,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz",
|
||||
"integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c= sha512-9Y0g0Q8rmSt+H33DfKv7FOc3v+iRI+o1lbzt8jGcIosYW37IIW/2XVYq5NPdmaD5NQ59Nk26Kl/vZbwW9Fr8vg=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
|
||||
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
@@ -216,18 +209,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz",
|
||||
"integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.2"
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"find-up": "^5.0.0",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"minimatch": "^10.2.1",
|
||||
"minimatch": "^5.1.6",
|
||||
"request-light": "^0.7.0",
|
||||
"which": "^4.0.0",
|
||||
"which-pm": "^2.1.1",
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { minimatch } from 'minimatch';
|
||||
import minimatch from 'minimatch';
|
||||
import { Utils } from 'vscode-uri';
|
||||
import { findPreferredPM } from './preferred-pm';
|
||||
import { readScripts } from './readScripts';
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ typings/**
|
||||
**/*.map
|
||||
.gitignore
|
||||
tsconfig*.json
|
||||
esbuild*.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);
|
||||
+12
-10
@@ -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);
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {},
|
||||
"exclude": [
|
||||
"./src/test/**"
|
||||
],
|
||||
"files": [
|
||||
"./src/extension.browser.ts"
|
||||
]
|
||||
}
|
||||
Generated
+921
-51
File diff suppressed because it is too large
Load Diff
+16
-12
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.110.0",
|
||||
"distro": "85914d5a600261a53306190177be48aa8f0cdfb4",
|
||||
"distro": "bd187e4508a244500eb533c56e5cccb6801a699c",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -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",
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
"name": "Apple"
|
||||
}
|
||||
},
|
||||
"providerExtensionId": "vscode.github-authentication",
|
||||
"providerUriSetting": "github-enterprise.uri",
|
||||
"providerScopes": [
|
||||
[
|
||||
|
||||
Generated
+48
-48
@@ -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/*"
|
||||
|
||||
+10
-10
@@ -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",
|
||||
|
||||
Generated
+44
-44
@@ -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/*"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -576,7 +576,7 @@ for (let i = 0; i <= KeyCode.MAX_VALUE; i++) {
|
||||
[1, ScanCode.Insert, 'Insert', KeyCode.Insert, 'Insert', 45, 'VK_INSERT', empty, empty],
|
||||
[1, ScanCode.Home, 'Home', KeyCode.Home, 'Home', 36, 'VK_HOME', empty, empty],
|
||||
[1, ScanCode.PageUp, 'PageUp', KeyCode.PageUp, 'PageUp', 33, 'VK_PRIOR', empty, empty],
|
||||
[1, ScanCode.Delete, 'Delete', KeyCode.Delete, 'Delete', 46, 'VK_DELETE', empty, empty],
|
||||
[1, ScanCode.Delete, 'Delete', KeyCode.Delete, 'Del', 46, 'VK_DELETE', 'Delete', empty],
|
||||
[1, ScanCode.End, 'End', KeyCode.End, 'End', 35, 'VK_END', empty, empty],
|
||||
[1, ScanCode.PageDown, 'PageDown', KeyCode.PageDown, 'PageDown', 34, 'VK_NEXT', empty, empty],
|
||||
[1, ScanCode.ArrowRight, 'ArrowRight', KeyCode.RightArrow, 'RightArrow', 39, 'VK_RIGHT', 'Right', empty],
|
||||
@@ -808,6 +808,8 @@ export namespace KeyCodeUtils {
|
||||
return 'Left';
|
||||
case KeyCode.RightArrow:
|
||||
return 'Right';
|
||||
case KeyCode.Delete:
|
||||
return 'Delete';
|
||||
}
|
||||
|
||||
return uiMap.keyCodeToStr(keyCode);
|
||||
|
||||
@@ -360,6 +360,7 @@ export interface IDefaultChatAgent {
|
||||
apple: { id: string; name: string };
|
||||
};
|
||||
|
||||
readonly providerExtensionId: string;
|
||||
readonly providerUriSetting: string;
|
||||
readonly providerScopes: string[][];
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -336,6 +336,11 @@ export interface IActionListOptions {
|
||||
*/
|
||||
readonly showFilter?: boolean;
|
||||
|
||||
/**
|
||||
* Placeholder text for the filter input.
|
||||
*/
|
||||
readonly filterPlaceholder?: string;
|
||||
|
||||
/**
|
||||
* Section IDs that should be collapsed by default.
|
||||
*/
|
||||
@@ -480,7 +485,7 @@ export class ActionList<T> extends Disposable {
|
||||
this._filterInput = document.createElement('input');
|
||||
this._filterInput.type = 'text';
|
||||
this._filterInput.className = 'action-list-filter-input';
|
||||
this._filterInput.placeholder = localize('actionList.filter.placeholder', "Search...");
|
||||
this._filterInput.placeholder = this._options?.filterPlaceholder ?? localize('actionList.filter.placeholder', "Search...");
|
||||
this._filterInput.setAttribute('aria-label', localize('actionList.filter.ariaLabel', "Filter items"));
|
||||
this._filterContainer.appendChild(this._filterInput);
|
||||
|
||||
@@ -488,33 +493,6 @@ export class ActionList<T> extends Disposable {
|
||||
this._filterText = this._filterInput!.value;
|
||||
this._applyFilter();
|
||||
}));
|
||||
|
||||
// Keyboard navigation from filter input
|
||||
this._register(dom.addDisposableListener(this._filterInput, 'keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
this._list.domFocus();
|
||||
const lastIndex = this._list.length - 1;
|
||||
if (lastIndex >= 0) {
|
||||
this._list.focusLast(undefined, this.focusCondition);
|
||||
}
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
this._list.domFocus();
|
||||
this.focusNext();
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.acceptSelected();
|
||||
} else if (e.key === 'Escape') {
|
||||
if (this._filterText) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this._filterInput!.value = '';
|
||||
this._filterText = '';
|
||||
this._applyFilter();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this._applyFilter();
|
||||
@@ -546,10 +524,10 @@ export class ActionList<T> extends Disposable {
|
||||
} else {
|
||||
this._collapsedSections.add(section);
|
||||
}
|
||||
this._applyFilter(true);
|
||||
this._applyFilter();
|
||||
}
|
||||
|
||||
private _applyFilter(reposition?: boolean): void {
|
||||
private _applyFilter(): void {
|
||||
const filterLower = this._filterText.toLowerCase();
|
||||
const isFiltering = filterLower.length > 0;
|
||||
const visible: IActionListItem<T>[] = [];
|
||||
@@ -647,9 +625,7 @@ export class ActionList<T> extends Disposable {
|
||||
}
|
||||
}
|
||||
// Reposition the context view so the widget grows in the correct direction
|
||||
if (reposition) {
|
||||
this._contextViewService.layout();
|
||||
}
|
||||
this._contextViewService.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,6 +684,16 @@ export class ActionList<T> extends Disposable {
|
||||
this._contextViewService.hideContextView();
|
||||
}
|
||||
|
||||
clearFilter(): boolean {
|
||||
if (this._filterInput && this._filterText) {
|
||||
this._filterInput.value = '';
|
||||
this._filterText = '';
|
||||
this._applyFilter();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private hasDynamicHeight(): boolean {
|
||||
if (this._options?.showFilter) {
|
||||
return true;
|
||||
@@ -867,17 +853,41 @@ export class ActionList<T> extends Disposable {
|
||||
}
|
||||
|
||||
focusPrevious() {
|
||||
if (this._filterInput && dom.isActiveElement(this._filterInput)) {
|
||||
this._list.domFocus();
|
||||
this._list.focusLast(undefined, this.focusCondition);
|
||||
return;
|
||||
}
|
||||
const previousFocus = this._list.getFocus();
|
||||
this._list.focusPrevious(1, true, undefined, this.focusCondition);
|
||||
const focused = this._list.getFocus();
|
||||
if (focused.length > 0) {
|
||||
// If focus wrapped (was at first focusable, now at last), move to filter instead
|
||||
if (this._filterInput && previousFocus.length > 0 && focused[0] > previousFocus[0]) {
|
||||
this._list.setFocus([]);
|
||||
this._filterInput.focus();
|
||||
return;
|
||||
}
|
||||
this._list.reveal(focused[0]);
|
||||
}
|
||||
}
|
||||
|
||||
focusNext() {
|
||||
if (this._filterInput && dom.isActiveElement(this._filterInput)) {
|
||||
this._list.domFocus();
|
||||
this._list.focusFirst(undefined, this.focusCondition);
|
||||
return;
|
||||
}
|
||||
const previousFocus = this._list.getFocus();
|
||||
this._list.focusNext(1, true, undefined, this.focusCondition);
|
||||
const focused = this._list.getFocus();
|
||||
if (focused.length > 0) {
|
||||
// If focus wrapped (was at last focusable, now at first), move to filter instead
|
||||
if (this._filterInput && previousFocus.length > 0 && focused[0] < previousFocus[0]) {
|
||||
this._list.setFocus([]);
|
||||
this._filterInput.focus();
|
||||
return;
|
||||
}
|
||||
this._list.reveal(focused[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,10 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
|
||||
return this._list?.value?.toggleFocusedSection() ?? false;
|
||||
}
|
||||
|
||||
clearFilter(): boolean {
|
||||
return this._list?.value?.clearFilter() ?? false;
|
||||
}
|
||||
|
||||
hide(didCancel?: boolean) {
|
||||
this._list.value?.hide(didCancel);
|
||||
this._list.clear();
|
||||
@@ -220,6 +224,29 @@ registerAction2(class extends Action2 {
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'clearFilterCodeActionWidget',
|
||||
title: localize2('clearFilterCodeActionWidget.title', "Clear action widget filter"),
|
||||
precondition: ContextKeyExpr.and(ActionWidgetContextKeys.Visible, ActionWidgetContextKeys.FilterFocused),
|
||||
keybinding: {
|
||||
weight: weight + 1,
|
||||
primary: KeyCode.Escape,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
const widgetService = accessor.get(IActionWidgetService);
|
||||
if (widgetService instanceof ActionWidgetService) {
|
||||
if (!widgetService.clearFilter()) {
|
||||
widgetService.hide(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
|
||||
@@ -13,10 +13,29 @@ export const IMcpGatewayService = createDecorator<IMcpGatewayService>('IMcpGatew
|
||||
export const McpGatewayChannelName = 'mcpGateway';
|
||||
export const McpGatewayToolBrokerChannelName = 'mcpGatewayToolBroker';
|
||||
|
||||
export interface IGatewayCallToolResult {
|
||||
result: MCP.CallToolResult;
|
||||
serverIndex: number;
|
||||
}
|
||||
|
||||
export interface IGatewayServerResources {
|
||||
serverIndex: number;
|
||||
resources: readonly MCP.Resource[];
|
||||
}
|
||||
|
||||
export interface IGatewayServerResourceTemplates {
|
||||
serverIndex: number;
|
||||
resourceTemplates: readonly MCP.ResourceTemplate[];
|
||||
}
|
||||
|
||||
export interface IMcpGatewayToolInvoker {
|
||||
readonly onDidChangeTools: Event<void>;
|
||||
readonly onDidChangeResources: Event<void>;
|
||||
listTools(): Promise<readonly MCP.Tool[]>;
|
||||
callTool(name: string, args: Record<string, unknown>): Promise<MCP.CallToolResult>;
|
||||
callTool(name: string, args: Record<string, unknown>): Promise<IGatewayCallToolResult>;
|
||||
listResources(): Promise<readonly IGatewayServerResources[]>;
|
||||
readResource(serverIndex: number, uri: string): Promise<MCP.ReadResourceResult>;
|
||||
listResourceTemplates(): Promise<readonly IGatewayServerResourceTemplates[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Event } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { IPCServer, IServerChannel } from '../../../base/parts/ipc/common/ipc.js';
|
||||
import { IMcpGatewayService, McpGatewayToolBrokerChannelName } from '../common/mcpGateway.js';
|
||||
import { IGatewayCallToolResult, IGatewayServerResources, IGatewayServerResourceTemplates, IMcpGatewayService, McpGatewayToolBrokerChannelName } from '../common/mcpGateway.js';
|
||||
import { MCP } from '../common/modelContextProtocol.js';
|
||||
|
||||
/**
|
||||
@@ -35,8 +35,12 @@ export class McpGatewayChannel<TContext> extends Disposable implements IServerCh
|
||||
const brokerChannel = ipcChannelForContext(this._ipcServer, ctx);
|
||||
const result = await this.mcpGatewayService.createGateway(ctx, {
|
||||
onDidChangeTools: brokerChannel.listen<void>('onDidChangeTools'),
|
||||
onDidChangeResources: brokerChannel.listen<void>('onDidChangeResources'),
|
||||
listTools: () => brokerChannel.call<readonly MCP.Tool[]>('listTools'),
|
||||
callTool: (name, callArgs) => brokerChannel.call<MCP.CallToolResult>('callTool', { name, args: callArgs }),
|
||||
callTool: (name, callArgs) => brokerChannel.call<IGatewayCallToolResult>('callTool', { name, args: callArgs }),
|
||||
listResources: () => brokerChannel.call<readonly IGatewayServerResources[]>('listResources'),
|
||||
readResource: (serverIndex, uri) => brokerChannel.call<MCP.ReadResourceResult>('readResource', { serverIndex, uri }),
|
||||
listResourceTemplates: () => brokerChannel.call<readonly IGatewayServerResourceTemplates[]>('listResourceTemplates'),
|
||||
});
|
||||
return result as T;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,56 @@ const MCP_INVALID_REQUEST = -32600;
|
||||
const MCP_METHOD_NOT_FOUND = -32601;
|
||||
const MCP_INVALID_PARAMS = -32602;
|
||||
|
||||
const GATEWAY_URI_AUTHORITY_RE = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/?#]*)(.*)/;
|
||||
|
||||
/**
|
||||
* Encodes a resource URI for the gateway by appending `-{serverIndex}` to the authority.
|
||||
* This namespaces resources from different MCP servers served through the same gateway.
|
||||
*/
|
||||
export function encodeGatewayResourceUri(uri: string, serverIndex: number): string {
|
||||
const match = uri.match(GATEWAY_URI_AUTHORITY_RE);
|
||||
if (!match) {
|
||||
return uri;
|
||||
}
|
||||
const [, prefix, authority, rest] = match;
|
||||
return `${prefix}${authority}-${serverIndex}${rest}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a gateway-encoded resource URI, extracting the server index and original URI.
|
||||
*/
|
||||
export function decodeGatewayResourceUri(uri: string): { serverIndex: number; originalUri: string } {
|
||||
const match = uri.match(GATEWAY_URI_AUTHORITY_RE);
|
||||
if (!match) {
|
||||
throw new JsonRpcError(MCP_INVALID_PARAMS, `Invalid resource URI: ${uri}`);
|
||||
}
|
||||
const [, prefix, authority, rest] = match;
|
||||
const suffixMatch = authority.match(/^(.*)-([0-9]+)$/);
|
||||
if (!suffixMatch) {
|
||||
throw new JsonRpcError(MCP_INVALID_PARAMS, `Invalid gateway resource URI (no server index): ${uri}`);
|
||||
}
|
||||
const [, originalAuthority, indexStr] = suffixMatch;
|
||||
return {
|
||||
serverIndex: parseInt(indexStr, 10),
|
||||
originalUri: `${prefix}${originalAuthority}${rest}`,
|
||||
};
|
||||
}
|
||||
|
||||
function encodeResourceUrisInContent(content: MCP.ContentBlock[], serverIndex: number): MCP.ContentBlock[] {
|
||||
return content.map(block => {
|
||||
if (block.type === 'resource_link') {
|
||||
return { ...block, uri: encodeGatewayResourceUri(block.uri, serverIndex) };
|
||||
}
|
||||
if (block.type === 'resource') {
|
||||
return {
|
||||
...block,
|
||||
resource: { ...block.resource, uri: encodeGatewayResourceUri(block.resource.uri, serverIndex) },
|
||||
};
|
||||
}
|
||||
return block;
|
||||
});
|
||||
}
|
||||
|
||||
export class McpGatewaySession extends Disposable {
|
||||
private readonly _rpc: JsonRpcProtocol;
|
||||
private readonly _sseClients = new Set<http.ServerResponse>();
|
||||
@@ -50,6 +100,14 @@ export class McpGatewaySession extends Disposable {
|
||||
|
||||
this._rpc.sendNotification({ method: 'notifications/tools/list_changed' });
|
||||
}));
|
||||
|
||||
this._register(this._toolInvoker.onDidChangeResources(() => {
|
||||
if (!this._isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._rpc.sendNotification({ method: 'notifications/resources/list_changed' });
|
||||
}));
|
||||
}
|
||||
|
||||
public attachSseClient(_req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
@@ -148,6 +206,12 @@ export class McpGatewaySession extends Disposable {
|
||||
return this._handleListTools();
|
||||
case 'tools/call':
|
||||
return this._handleCallTool(request);
|
||||
case 'resources/list':
|
||||
return this._handleListResources();
|
||||
case 'resources/read':
|
||||
return this._handleReadResource(request);
|
||||
case 'resources/templates/list':
|
||||
return this._handleListResourceTemplates();
|
||||
default:
|
||||
throw new JsonRpcError(MCP_METHOD_NOT_FOUND, `Method not found: ${request.method}`);
|
||||
}
|
||||
@@ -157,6 +221,7 @@ export class McpGatewaySession extends Disposable {
|
||||
if (notification.method === 'notifications/initialized') {
|
||||
this._isInitialized = true;
|
||||
this._rpc.sendNotification({ method: 'notifications/tools/list_changed' });
|
||||
this._rpc.sendNotification({ method: 'notifications/resources/list_changed' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +232,9 @@ export class McpGatewaySession extends Disposable {
|
||||
tools: {
|
||||
listChanged: true,
|
||||
},
|
||||
resources: {
|
||||
listChanged: true,
|
||||
},
|
||||
},
|
||||
serverInfo: {
|
||||
name: 'VS Code MCP Gateway',
|
||||
@@ -175,7 +243,7 @@ export class McpGatewaySession extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
private _handleCallTool(request: IJsonRpcRequest): unknown {
|
||||
private async _handleCallTool(request: IJsonRpcRequest): Promise<MCP.CallToolResult> {
|
||||
const params = typeof request.params === 'object' && request.params ? request.params as Record<string, unknown> : undefined;
|
||||
if (!params || typeof params.name !== 'string') {
|
||||
throw new JsonRpcError(MCP_INVALID_PARAMS, 'Missing tool call params');
|
||||
@@ -189,16 +257,71 @@ export class McpGatewaySession extends Disposable {
|
||||
? params.arguments as Record<string, unknown>
|
||||
: {};
|
||||
|
||||
return this._toolInvoker.callTool(params.name, argumentsValue).catch(error => {
|
||||
try {
|
||||
const { result, serverIndex } = await this._toolInvoker.callTool(params.name, argumentsValue);
|
||||
return {
|
||||
...result,
|
||||
content: encodeResourceUrisInContent(result.content, serverIndex),
|
||||
};
|
||||
} catch (error) {
|
||||
this._logService.error('[McpGatewayService] Tool call invocation failed', error);
|
||||
throw new JsonRpcError(MCP_INVALID_PARAMS, String(error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _handleListTools(): unknown {
|
||||
return this._toolInvoker.listTools()
|
||||
.then(tools => ({ tools }));
|
||||
}
|
||||
|
||||
private async _handleListResources(): Promise<MCP.ListResourcesResult> {
|
||||
const serverResults = await this._toolInvoker.listResources();
|
||||
const allResources: MCP.Resource[] = [];
|
||||
for (const { serverIndex, resources } of serverResults) {
|
||||
for (const resource of resources) {
|
||||
allResources.push({
|
||||
...resource,
|
||||
uri: encodeGatewayResourceUri(resource.uri, serverIndex),
|
||||
});
|
||||
}
|
||||
}
|
||||
return { resources: allResources };
|
||||
}
|
||||
|
||||
private async _handleReadResource(request: IJsonRpcRequest): Promise<MCP.ReadResourceResult> {
|
||||
const params = typeof request.params === 'object' && request.params ? request.params as Record<string, unknown> : undefined;
|
||||
if (!params || typeof params.uri !== 'string') {
|
||||
throw new JsonRpcError(MCP_INVALID_PARAMS, 'Missing resource URI');
|
||||
}
|
||||
|
||||
const { serverIndex, originalUri } = decodeGatewayResourceUri(params.uri);
|
||||
try {
|
||||
const result = await this._toolInvoker.readResource(serverIndex, originalUri);
|
||||
return {
|
||||
contents: result.contents.map(content => ({
|
||||
...content,
|
||||
uri: encodeGatewayResourceUri(content.uri, serverIndex),
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
this._logService.error('[McpGatewayService] Resource read failed', error);
|
||||
throw new JsonRpcError(MCP_INVALID_PARAMS, String(error));
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleListResourceTemplates(): Promise<MCP.ListResourceTemplatesResult> {
|
||||
const serverResults = await this._toolInvoker.listResourceTemplates();
|
||||
const allTemplates: MCP.ResourceTemplate[] = [];
|
||||
for (const { serverIndex, resourceTemplates } of serverResults) {
|
||||
for (const template of resourceTemplates) {
|
||||
allTemplates.push({
|
||||
...template,
|
||||
uriTemplate: encodeGatewayResourceUri(template.uriTemplate, serverIndex),
|
||||
});
|
||||
}
|
||||
}
|
||||
return { resourceTemplates: allTemplates };
|
||||
}
|
||||
}
|
||||
|
||||
export function isInitializeMessage(message: JsonRpcMessage | JsonRpcMessage[]): boolean {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IJsonRpcErrorResponse, IJsonRpcSuccessResponse } from '../../../../base
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { NullLogService } from '../../../log/common/log.js';
|
||||
import { MCP } from '../../common/modelContextProtocol.js';
|
||||
import { McpGatewaySession } from '../../node/mcpGatewaySession.js';
|
||||
import { decodeGatewayResourceUri, encodeGatewayResourceUri, McpGatewaySession } from '../../node/mcpGatewaySession.js';
|
||||
|
||||
class TestServerResponse extends EventEmitter {
|
||||
public statusCode: number | undefined;
|
||||
@@ -48,6 +48,7 @@ suite('McpGatewaySession', () => {
|
||||
|
||||
function createInvoker() {
|
||||
const onDidChangeTools = new Emitter<void>();
|
||||
const onDidChangeResources = new Emitter<void>();
|
||||
const tools: readonly MCP.Tool[] = [{
|
||||
name: 'test_tool',
|
||||
description: 'Test tool',
|
||||
@@ -59,20 +60,35 @@ suite('McpGatewaySession', () => {
|
||||
}
|
||||
}];
|
||||
|
||||
const resources: readonly MCP.Resource[] = [{
|
||||
uri: 'file:///test/resource.txt',
|
||||
name: 'resource.txt',
|
||||
}];
|
||||
|
||||
return {
|
||||
onDidChangeTools,
|
||||
onDidChangeResources,
|
||||
invoker: {
|
||||
onDidChangeTools: onDidChangeTools.event,
|
||||
onDidChangeResources: onDidChangeResources.event,
|
||||
listTools: async () => tools,
|
||||
callTool: async (_name: string, args: Record<string, unknown>): Promise<MCP.CallToolResult> => ({
|
||||
content: [{ type: 'text', text: `Hello, ${typeof args.name === 'string' ? args.name : 'World'}!` }]
|
||||
})
|
||||
callTool: async (_name: string, args: Record<string, unknown>) => ({
|
||||
result: {
|
||||
content: [{ type: 'text' as const, text: `Hello, ${typeof args.name === 'string' ? args.name : 'World'}!` }]
|
||||
},
|
||||
serverIndex: 0,
|
||||
}),
|
||||
listResources: async () => [{ serverIndex: 0, resources }],
|
||||
readResource: async (_serverIndex: number, _uri: string) => ({
|
||||
contents: [{ uri: 'file:///test/resource.txt', text: 'hello world', mimeType: 'text/plain' }],
|
||||
}),
|
||||
listResourceTemplates: async () => [{ serverIndex: 0, resourceTemplates: [{ uriTemplate: 'file:///test/{name}', name: 'Test Template' }] }],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('returns initialize result', async () => {
|
||||
const { invoker, onDidChangeTools } = createInvoker();
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-1', new NullLogService(), () => { }, invoker);
|
||||
|
||||
const responses = await session.handleIncoming({
|
||||
@@ -93,10 +109,11 @@ suite('McpGatewaySession', () => {
|
||||
assert.strictEqual((response.result as { protocolVersion: string }).protocolVersion, '2025-11-25');
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('rejects non-initialize requests before initialized notification', async () => {
|
||||
const { invoker, onDidChangeTools } = createInvoker();
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-2', new NullLogService(), () => { }, invoker);
|
||||
|
||||
const responses = await session.handleIncoming({
|
||||
@@ -112,10 +129,11 @@ suite('McpGatewaySession', () => {
|
||||
assert.strictEqual(response.error.code, -32600);
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('serves tools/list and tools/call after initialized notification', async () => {
|
||||
const { invoker, onDidChangeTools } = createInvoker();
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-3', new NullLogService(), () => { }, invoker);
|
||||
|
||||
await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' });
|
||||
@@ -145,10 +163,11 @@ suite('McpGatewaySession', () => {
|
||||
assert.strictEqual(text, 'Hello, VS Code!');
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('broadcasts notifications to attached SSE clients', async () => {
|
||||
const { invoker, onDidChangeTools } = createInvoker();
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-4', new NullLogService(), () => { }, invoker);
|
||||
const response = new TestServerResponse();
|
||||
|
||||
@@ -161,12 +180,14 @@ suite('McpGatewaySession', () => {
|
||||
assert.ok(response.writes.some(chunk => chunk.includes(': connected')));
|
||||
assert.ok(response.writes.some(chunk => chunk.includes('event: message')));
|
||||
assert.ok(response.writes.some(chunk => chunk.includes('notifications/tools/list_changed')));
|
||||
assert.ok(response.writes.some(chunk => chunk.includes('notifications/resources/list_changed')));
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('emits list changed on tool invoker changes', async () => {
|
||||
const { invoker, onDidChangeTools } = createInvoker();
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-5', new NullLogService(), () => { }, invoker);
|
||||
const response = new TestServerResponse();
|
||||
|
||||
@@ -181,10 +202,11 @@ suite('McpGatewaySession', () => {
|
||||
assert.ok(response.writes.slice(writesBefore).some(chunk => chunk.includes('notifications/tools/list_changed')));
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('disposes attached SSE clients and callback', () => {
|
||||
const { invoker, onDidChangeTools } = createInvoker();
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
let disposed = false;
|
||||
const session = new McpGatewaySession('session-6', new NullLogService(), () => {
|
||||
disposed = true;
|
||||
@@ -197,5 +219,145 @@ suite('McpGatewaySession', () => {
|
||||
assert.strictEqual(response.writableEnded, true);
|
||||
assert.strictEqual(disposed, true);
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('emits resources list changed on resource invoker changes', async () => {
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-7', new NullLogService(), () => { }, invoker);
|
||||
const response = new TestServerResponse();
|
||||
|
||||
session.attachSseClient({} as http.IncomingMessage, response as unknown as http.ServerResponse);
|
||||
await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' });
|
||||
await session.handleIncoming({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
||||
|
||||
const writesBefore = response.writes.length;
|
||||
onDidChangeResources.fire();
|
||||
|
||||
assert.ok(response.writes.length > writesBefore);
|
||||
assert.ok(response.writes.slice(writesBefore).some(chunk => chunk.includes('notifications/resources/list_changed')));
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('serves resources/list with encoded URIs', async () => {
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-8', new NullLogService(), () => { }, invoker);
|
||||
|
||||
await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' });
|
||||
await session.handleIncoming({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
||||
|
||||
const responses = await session.handleIncoming({ jsonrpc: '2.0', id: 2, method: 'resources/list' });
|
||||
const response = responses[0] as IJsonRpcSuccessResponse;
|
||||
const resources = (response.result as { resources: Array<{ uri: string; name: string }> }).resources;
|
||||
assert.strictEqual(resources.length, 1);
|
||||
assert.strictEqual(resources[0].uri, 'file://-0/test/resource.txt');
|
||||
assert.strictEqual(resources[0].name, 'resource.txt');
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('serves resources/read with URI decoding and re-encoding', async () => {
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-9', new NullLogService(), () => { }, invoker);
|
||||
|
||||
await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' });
|
||||
await session.handleIncoming({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
||||
|
||||
const responses = await session.handleIncoming({
|
||||
jsonrpc: '2.0',
|
||||
id: 2,
|
||||
method: 'resources/read',
|
||||
params: { uri: 'file://-0/test/resource.txt' },
|
||||
});
|
||||
const response = responses[0] as IJsonRpcSuccessResponse;
|
||||
const contents = (response.result as { contents: Array<{ uri: string; text: string }> }).contents;
|
||||
assert.strictEqual(contents.length, 1);
|
||||
assert.strictEqual(contents[0].uri, 'file://-0/test/resource.txt');
|
||||
assert.strictEqual(contents[0].text, 'hello world');
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
|
||||
test('serves resources/templates/list with encoded URI templates', async () => {
|
||||
const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker();
|
||||
const session = new McpGatewaySession('session-10', new NullLogService(), () => { }, invoker);
|
||||
|
||||
await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' });
|
||||
await session.handleIncoming({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
||||
|
||||
const responses = await session.handleIncoming({ jsonrpc: '2.0', id: 2, method: 'resources/templates/list' });
|
||||
const response = responses[0] as IJsonRpcSuccessResponse;
|
||||
const templates = (response.result as { resourceTemplates: Array<{ uriTemplate: string; name: string }> }).resourceTemplates;
|
||||
assert.strictEqual(templates.length, 1);
|
||||
assert.strictEqual(templates[0].uriTemplate, 'file://-0/test/{name}');
|
||||
assert.strictEqual(templates[0].name, 'Test Template');
|
||||
session.dispose();
|
||||
onDidChangeTools.dispose();
|
||||
onDidChangeResources.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
suite('Gateway Resource URI encoding', () => {
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
test('encodes and decodes URI with authority', () => {
|
||||
const encoded = encodeGatewayResourceUri('https://example.com/resource', 3);
|
||||
assert.strictEqual(encoded, 'https://example.com-3/resource');
|
||||
const decoded = decodeGatewayResourceUri(encoded);
|
||||
assert.strictEqual(decoded.serverIndex, 3);
|
||||
assert.strictEqual(decoded.originalUri, 'https://example.com/resource');
|
||||
});
|
||||
|
||||
test('encodes and decodes URI with empty authority', () => {
|
||||
const encoded = encodeGatewayResourceUri('file:///path/to/file', 0);
|
||||
assert.strictEqual(encoded, 'file://-0/path/to/file');
|
||||
const decoded = decodeGatewayResourceUri(encoded);
|
||||
assert.strictEqual(decoded.serverIndex, 0);
|
||||
assert.strictEqual(decoded.originalUri, 'file:///path/to/file');
|
||||
});
|
||||
|
||||
test('encodes and decodes URI with authority containing hyphens', () => {
|
||||
const encoded = encodeGatewayResourceUri('https://my-server.example.com/res', 12);
|
||||
assert.strictEqual(encoded, 'https://my-server.example.com-12/res');
|
||||
const decoded = decodeGatewayResourceUri(encoded);
|
||||
assert.strictEqual(decoded.serverIndex, 12);
|
||||
assert.strictEqual(decoded.originalUri, 'https://my-server.example.com/res');
|
||||
});
|
||||
|
||||
test('encodes and decodes URI with port', () => {
|
||||
const encoded = encodeGatewayResourceUri('http://localhost:8080/api', 5);
|
||||
assert.strictEqual(encoded, 'http://localhost:8080-5/api');
|
||||
const decoded = decodeGatewayResourceUri(encoded);
|
||||
assert.strictEqual(decoded.serverIndex, 5);
|
||||
assert.strictEqual(decoded.originalUri, 'http://localhost:8080/api');
|
||||
});
|
||||
|
||||
test('encodes and decodes URI with query and fragment', () => {
|
||||
const encoded = encodeGatewayResourceUri('https://example.com/resource?q=1#section', 2);
|
||||
assert.strictEqual(encoded, 'https://example.com-2/resource?q=1#section');
|
||||
const decoded = decodeGatewayResourceUri(encoded);
|
||||
assert.strictEqual(decoded.serverIndex, 2);
|
||||
assert.strictEqual(decoded.originalUri, 'https://example.com/resource?q=1#section');
|
||||
});
|
||||
|
||||
test('encodes and decodes custom scheme URIs', () => {
|
||||
const encoded = encodeGatewayResourceUri('custom://myhost/path', 7);
|
||||
assert.strictEqual(encoded, 'custom://myhost-7/path');
|
||||
const decoded = decodeGatewayResourceUri(encoded);
|
||||
assert.strictEqual(decoded.serverIndex, 7);
|
||||
assert.strictEqual(decoded.originalUri, 'custom://myhost/path');
|
||||
});
|
||||
|
||||
test('returns URI unchanged if no scheme match', () => {
|
||||
const encoded = encodeGatewayResourceUri('not-a-uri', 1);
|
||||
assert.strictEqual(encoded, 'not-a-uri');
|
||||
});
|
||||
|
||||
test('throws on decode of URI without server index suffix', () => {
|
||||
assert.throws(() => decodeGatewayResourceUri('https://example.com/resource'));
|
||||
});
|
||||
});
|
||||
|
||||
+27
-90
@@ -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 |
|
||||
| Press Escape | Close all editors, hide modal |
|
||||
|
||||
#### Modal Sizing
|
||||
#### Configuration
|
||||
|
||||
Modal dimensions are calculated in TypeScript rather than CSS. The `EditorModal.layout()` method receives workbench dimensions and computes the modal size with constraints:
|
||||
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)
|
||||
|
||||
| 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
|
||||
```
|
||||
|
||||
#### 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<boolean>;
|
||||
|
||||
// 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 `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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -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<boolean>());
|
||||
readonly onDidChangeVisibility: Event<boolean> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,7 +348,6 @@ export class AuxiliaryTitlebarPart extends TitlebarPart implements IAuxiliaryTit
|
||||
|
||||
constructor(
|
||||
readonly container: HTMLElement,
|
||||
editorGroupsContainer: IEditorGroupsContainer,
|
||||
private readonly mainTitlebar: TitlebarPart,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@@ -418,8 +417,8 @@ export class TitleService extends MultiWindowParts<TitlebarPart> implements ITit
|
||||
return titlebarPart;
|
||||
}
|
||||
|
||||
protected doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): TitlebarPart & IAuxiliaryTitlebarPart {
|
||||
return instantiationService.createInstance(AuxiliaryTitlebarPart, container, editorGroupsContainer, this.mainPart);
|
||||
protected doCreateAuxiliaryTitlebarPart(container: HTMLElement, _editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): TitlebarPart & IAuxiliaryTitlebarPart {
|
||||
return instantiationService.createInstance(AuxiliaryTitlebarPart, container, this.mainPart);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -307,7 +307,6 @@ class AccountWidgetContribution extends Disposable implements IWorkbenchContribu
|
||||
when: ContextKeyExpr.or(
|
||||
CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready),
|
||||
CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload),
|
||||
CONTEXT_UPDATE_STATE.isEqualTo(StateType.CheckingForUpdates),
|
||||
CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloading),
|
||||
CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded),
|
||||
CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating),
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './agentFeedbackEditorInputContribution.js';
|
||||
import './agentFeedbackGlyphMarginContribution.js';
|
||||
import './agentFeedbackLineDecorationContribution.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
|
||||
import { AgentFeedbackService, IAgentFeedbackService } from './agentFeedbackService.js';
|
||||
import { AgentFeedbackAttachmentContribution } from './agentFeedbackAttachment.js';
|
||||
import { AgentFeedbackAttachmentWidget } from './agentFeedbackAttachmentWidget.js';
|
||||
import { AgentFeedbackEditorOverlay } from './agentFeedbackEditorOverlay.js';
|
||||
import { registerAgentFeedbackEditorActions } from './agentFeedbackEditorActions.js';
|
||||
import { IChatAttachmentWidgetRegistry } from '../../../../workbench/contrib/chat/browser/attachments/chatAttachmentWidgetRegistry.js';
|
||||
import { IAgentFeedbackVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
|
||||
|
||||
registerWorkbenchContribution2(AgentFeedbackEditorOverlay.ID, AgentFeedbackEditorOverlay, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(AgentFeedbackAttachmentContribution.ID, AgentFeedbackAttachmentContribution, WorkbenchPhase.AfterRestored);
|
||||
@@ -18,3 +21,14 @@ registerWorkbenchContribution2(AgentFeedbackAttachmentContribution.ID, AgentFeed
|
||||
registerAgentFeedbackEditorActions();
|
||||
|
||||
registerSingleton(IAgentFeedbackService, AgentFeedbackService, InstantiationType.Delayed);
|
||||
|
||||
// Register the custom attachment widget for agentFeedback attachments
|
||||
class AgentFeedbackAttachmentWidgetContribution {
|
||||
static readonly ID = 'workbench.contrib.agentFeedbackAttachmentWidgetFactory';
|
||||
constructor(@IChatAttachmentWidgetRegistry registry: IChatAttachmentWidgetRegistry) {
|
||||
registry.registerFactory('agentFeedback', (instantiationService, attachment, options, container) => {
|
||||
return instantiationService.createInstance(AgentFeedbackAttachmentWidget, attachment as IAgentFeedbackVariableEntry, options, container);
|
||||
});
|
||||
}
|
||||
}
|
||||
registerWorkbenchContribution2(AgentFeedbackAttachmentWidgetContribution.ID, AgentFeedbackAttachmentWidgetContribution, WorkbenchPhase.AfterRestored);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IAgentFeedbackService, IAgentFeedback } from './agentFeedbackService.js
|
||||
import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
|
||||
import { IAgentFeedbackVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
|
||||
|
||||
const ATTACHMENT_ID_PREFIX = 'agentFeedback:';
|
||||
export const ATTACHMENT_ID_PREFIX = 'agentFeedback:';
|
||||
|
||||
/**
|
||||
* Keeps the "N feedback items" attachment in the chat input in sync with the
|
||||
|
||||
+9
-12
@@ -8,7 +8,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { IEditorContribution } from '../../../../editor/common/editorCommon.js';
|
||||
import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { GlyphMarginLane, IModelDeltaDecoration, TrackedRangeStickiness } from '../../../../editor/common/model.js';
|
||||
import { IModelDeltaDecoration, TrackedRangeStickiness } from '../../../../editor/common/model.js';
|
||||
import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js';
|
||||
import { Range } from '../../../../editor/common/core/range.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
@@ -20,19 +20,15 @@ import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browse
|
||||
import { getSessionForResource } from './agentFeedbackEditorUtils.js';
|
||||
import { Selection } from '../../../../editor/common/core/selection.js';
|
||||
|
||||
const GLYPH_MARGIN_LANE = GlyphMarginLane.Left;
|
||||
|
||||
const feedbackGlyphDecoration = ModelDecorationOptions.register({
|
||||
description: 'agent-feedback-glyph',
|
||||
glyphMarginClassName: `${ThemeIcon.asClassName(Codicon.comment)} agent-feedback-glyph`,
|
||||
glyphMargin: { position: GLYPH_MARGIN_LANE },
|
||||
linesDecorationsClassName: `${ThemeIcon.asClassName(Codicon.comment)} agent-feedback-glyph`,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
});
|
||||
|
||||
const addFeedbackHintDecoration = ModelDecorationOptions.register({
|
||||
description: 'agent-feedback-add-hint',
|
||||
glyphMarginClassName: `${ThemeIcon.asClassName(Codicon.add)} agent-feedback-add-hint`,
|
||||
glyphMargin: { position: GLYPH_MARGIN_LANE },
|
||||
linesDecorationsClassName: `${ThemeIcon.asClassName(Codicon.add)} agent-feedback-add-hint`,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
});
|
||||
|
||||
@@ -117,9 +113,10 @@ export class AgentFeedbackGlyphMarginContribution extends Disposable implements
|
||||
return;
|
||||
}
|
||||
|
||||
const isLineDecoration = e.target.type === MouseTargetType.GUTTER_LINE_DECORATIONS && !e.target.detail.isAfterLines;
|
||||
const isContentArea = e.target.type === MouseTargetType.CONTENT_TEXT || e.target.type === MouseTargetType.CONTENT_EMPTY;
|
||||
if (e.target.position
|
||||
&& e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN
|
||||
&& !e.target.detail.isAfterLines
|
||||
&& (isLineDecoration || isContentArea)
|
||||
&& !this._feedbackLines.has(e.target.position.lineNumber)
|
||||
) {
|
||||
this._updateHintDecoration(e.target.position.lineNumber);
|
||||
@@ -150,7 +147,7 @@ export class AgentFeedbackGlyphMarginContribution extends Disposable implements
|
||||
|
||||
private _onMouseDown(e: IEditorMouseEvent): void {
|
||||
if (!e.target.position
|
||||
|| e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN
|
||||
|| e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS
|
||||
|| e.target.detail.isAfterLines
|
||||
|| !this._sessionResource
|
||||
) {
|
||||
@@ -174,9 +171,9 @@ export class AgentFeedbackGlyphMarginContribution extends Disposable implements
|
||||
const endColumn = model.getLineLastNonWhitespaceColumn(lineNumber);
|
||||
if (startColumn === 0 || endColumn === 0) {
|
||||
// Empty line - select the whole line range
|
||||
this._editor.setSelection(new Selection(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber)));
|
||||
this._editor.setSelection(new Selection(lineNumber, model.getLineMaxColumn(lineNumber), lineNumber, 1));
|
||||
} else {
|
||||
this._editor.setSelection(new Selection(lineNumber, startColumn, lineNumber, endColumn));
|
||||
this._editor.setSelection(new Selection(lineNumber, endColumn, lineNumber, startColumn));
|
||||
}
|
||||
this._editor.focus();
|
||||
}
|
||||
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './media/agentFeedbackLineDecoration.css';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { IEditorContribution } from '../../../../editor/common/editorCommon.js';
|
||||
import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { IModelDeltaDecoration, TrackedRangeStickiness } from '../../../../editor/common/model.js';
|
||||
import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js';
|
||||
import { Range } from '../../../../editor/common/core/range.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { IAgentFeedbackService } from './agentFeedbackService.js';
|
||||
import { IChatEditingService } from '../../../../workbench/contrib/chat/common/editing/chatEditingService.js';
|
||||
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
|
||||
import { getSessionForResource } from './agentFeedbackEditorUtils.js';
|
||||
import { Selection } from '../../../../editor/common/core/selection.js';
|
||||
|
||||
const feedbackLineDecoration = ModelDecorationOptions.register({
|
||||
description: 'agent-feedback-line-decoration',
|
||||
linesDecorationsClassName: `${ThemeIcon.asClassName(Codicon.comment)} agent-feedback-line-decoration`,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
});
|
||||
|
||||
const addFeedbackHintDecoration = ModelDecorationOptions.register({
|
||||
description: 'agent-feedback-add-hint',
|
||||
linesDecorationsClassName: `${ThemeIcon.asClassName(Codicon.add)} agent-feedback-add-hint`,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
});
|
||||
|
||||
export class AgentFeedbackLineDecorationContribution extends Disposable implements IEditorContribution {
|
||||
|
||||
static readonly ID = 'agentFeedback.lineDecorationContribution';
|
||||
|
||||
private readonly _feedbackDecorations;
|
||||
|
||||
private _hintDecorationId: string | null = null;
|
||||
private _hintLine = -1;
|
||||
private _sessionResource: URI | undefined;
|
||||
private _feedbackLines = new Set<number>();
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IAgentFeedbackService private readonly _agentFeedbackService: IAgentFeedbackService,
|
||||
@IChatEditingService private readonly _chatEditingService: IChatEditingService,
|
||||
@IAgentSessionsService private readonly _agentSessionsService: IAgentSessionsService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._feedbackDecorations = this._editor.createDecorationsCollection();
|
||||
|
||||
this._store.add(this._agentFeedbackService.onDidChangeFeedback(() => this._updateFeedbackDecorations()));
|
||||
this._store.add(this._editor.onDidChangeModel(() => this._onModelChanged()));
|
||||
this._store.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onMouseMove(e)));
|
||||
this._store.add(this._editor.onMouseLeave(() => this._updateHintDecoration(-1)));
|
||||
this._store.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onMouseDown(e)));
|
||||
|
||||
this._resolveSession();
|
||||
this._updateFeedbackDecorations();
|
||||
}
|
||||
|
||||
private _onModelChanged(): void {
|
||||
this._updateHintDecoration(-1);
|
||||
this._resolveSession();
|
||||
this._updateFeedbackDecorations();
|
||||
}
|
||||
|
||||
private _resolveSession(): void {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
this._sessionResource = undefined;
|
||||
return;
|
||||
}
|
||||
this._sessionResource = getSessionForResource(model.uri, this._chatEditingService, this._agentSessionsService);
|
||||
}
|
||||
|
||||
private _updateFeedbackDecorations(): void {
|
||||
if (!this._sessionResource) {
|
||||
this._feedbackDecorations.clear();
|
||||
this._feedbackLines.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const feedbackItems = this._agentFeedbackService.getFeedback(this._sessionResource);
|
||||
const decorations: IModelDeltaDecoration[] = [];
|
||||
const lines = new Set<number>();
|
||||
|
||||
for (const item of feedbackItems) {
|
||||
const model = this._editor.getModel();
|
||||
if (!model || item.resourceUri.toString() !== model.uri.toString()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const line = item.range.startLineNumber;
|
||||
lines.add(line);
|
||||
decorations.push({
|
||||
range: new Range(line, 1, line, 1),
|
||||
options: feedbackLineDecoration,
|
||||
});
|
||||
}
|
||||
|
||||
this._feedbackLines = lines;
|
||||
this._feedbackDecorations.set(decorations);
|
||||
}
|
||||
|
||||
private _onMouseMove(e: IEditorMouseEvent): void {
|
||||
if (!this._sessionResource) {
|
||||
this._updateHintDecoration(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
const isLineDecoration = e.target.type === MouseTargetType.GUTTER_LINE_DECORATIONS && !e.target.detail.isAfterLines;
|
||||
const isContentArea = e.target.type === MouseTargetType.CONTENT_TEXT || e.target.type === MouseTargetType.CONTENT_EMPTY;
|
||||
if (e.target.position
|
||||
&& (isLineDecoration || isContentArea)
|
||||
&& !this._feedbackLines.has(e.target.position.lineNumber)
|
||||
) {
|
||||
this._updateHintDecoration(e.target.position.lineNumber);
|
||||
} else {
|
||||
this._updateHintDecoration(-1);
|
||||
}
|
||||
}
|
||||
|
||||
private _updateHintDecoration(line: number): void {
|
||||
if (line === this._hintLine) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hintLine = line;
|
||||
this._editor.changeDecorations(accessor => {
|
||||
if (this._hintDecorationId) {
|
||||
accessor.removeDecoration(this._hintDecorationId);
|
||||
this._hintDecorationId = null;
|
||||
}
|
||||
if (line !== -1) {
|
||||
this._hintDecorationId = accessor.addDecoration(
|
||||
new Range(line, 1, line, 1),
|
||||
addFeedbackHintDecoration,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _onMouseDown(e: IEditorMouseEvent): void {
|
||||
if (!e.target.position
|
||||
|| e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS
|
||||
|| e.target.detail.isAfterLines
|
||||
|| !this._sessionResource
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lineNumber = e.target.position.lineNumber;
|
||||
|
||||
// Lines with existing feedback - do nothing
|
||||
if (this._feedbackLines.has(lineNumber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the line content and focus the editor
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
|
||||
const endColumn = model.getLineLastNonWhitespaceColumn(lineNumber);
|
||||
if (startColumn === 0 || endColumn === 0) {
|
||||
// Empty line - select the whole line range
|
||||
this._editor.setSelection(new Selection(lineNumber, model.getLineMaxColumn(lineNumber), lineNumber, 1));
|
||||
} else {
|
||||
this._editor.setSelection(new Selection(lineNumber, endColumn, lineNumber, startColumn));
|
||||
}
|
||||
this._editor.focus();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this._feedbackDecorations.clear();
|
||||
this._updateHintDecoration(-1);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(AgentFeedbackLineDecorationContribution.ID, AgentFeedbackLineDecorationContribution, EditorContributionInstantiation.Eventually);
|
||||
@@ -13,7 +13,6 @@ import { isEqual } from '../../../../base/common/resources.js';
|
||||
import { IChatEditingService } from '../../../../workbench/contrib/chat/common/editing/chatEditingService.js';
|
||||
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
|
||||
import { agentSessionContainsResource, editingEntriesContainResource } from '../../../../workbench/contrib/chat/browser/sessionResourceMatching.js';
|
||||
import { IChatWidget, IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
|
||||
|
||||
// --- Types --------------------------------------------------------------------
|
||||
|
||||
@@ -83,8 +82,6 @@ export interface IAgentFeedbackService {
|
||||
|
||||
// --- Implementation -----------------------------------------------------------
|
||||
|
||||
const AGENT_FEEDBACK_ATTACHMENT_ID_PREFIX = 'agentFeedback:';
|
||||
|
||||
export class AgentFeedbackService extends Disposable implements IAgentFeedbackService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
@@ -103,41 +100,8 @@ export class AgentFeedbackService extends Disposable implements IAgentFeedbackSe
|
||||
constructor(
|
||||
@IChatEditingService private readonly _chatEditingService: IChatEditingService,
|
||||
@IAgentSessionsService private readonly _agentSessionsService: IAgentSessionsService,
|
||||
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._registerChatWidgetListeners();
|
||||
}
|
||||
|
||||
private _registerChatWidgetListeners(): void {
|
||||
for (const widget of this._chatWidgetService.getAllWidgets()) {
|
||||
this._registerWidgetListeners(widget);
|
||||
}
|
||||
|
||||
this._store.add(this._chatWidgetService.onDidAddWidget(widget => {
|
||||
this._registerWidgetListeners(widget);
|
||||
}));
|
||||
}
|
||||
|
||||
private _registerWidgetListeners(widget: IChatWidget): void {
|
||||
this._store.add(widget.attachmentModel.onDidChange(e => {
|
||||
for (const deletedId of e.deleted) {
|
||||
if (!deletedId.startsWith(AGENT_FEEDBACK_ATTACHMENT_ID_PREFIX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sessionResourceString = deletedId.slice(AGENT_FEEDBACK_ATTACHMENT_ID_PREFIX.length);
|
||||
if (!sessionResourceString) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sessionResource = URI.parse(sessionResourceString);
|
||||
if (this.getFeedback(sessionResource).length > 0) {
|
||||
this.clearFeedback(sessionResource);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
addFeedback(sessionResource: URI, resourceUri: URI, range: IRange, text: string): IAgentFeedback {
|
||||
|
||||
@@ -3,24 +3,23 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-glyph,
|
||||
.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-add-hint {
|
||||
.monaco-editor .agent-feedback-glyph,
|
||||
.monaco-editor .agent-feedback-add-hint {
|
||||
border-radius: 3px;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-glyph {
|
||||
background-color: var(--vscode-editorGutter-commentGlyphForeground, var(--vscode-icon-foreground));
|
||||
color: var(--vscode-editor-background);
|
||||
.monaco-editor .agent-feedback-glyph {
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
}
|
||||
|
||||
.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-add-hint {
|
||||
.monaco-editor .agent-feedback-add-hint {
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-add-hint:hover {
|
||||
.monaco-editor .agent-feedback-add-hint:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .agent-feedback-line-decoration,
|
||||
.monaco-editor .agent-feedback-add-hint {
|
||||
border-radius: 3px;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.monaco-editor .agent-feedback-line-decoration {
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
}
|
||||
|
||||
.monaco-editor .agent-feedback-add-hint {
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.monaco-editor .agent-feedback-add-hint:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
+1
-1
@@ -247,7 +247,7 @@ export class AICustomizationManagementEditor extends EditorPane {
|
||||
layout: (width, _, height) => {
|
||||
this.sidebarContainer.style.width = `${width}px`;
|
||||
if (height !== undefined) {
|
||||
const listHeight = height - 24;
|
||||
const listHeight = height - 8;
|
||||
this.sectionsList.layout(listHeight, width);
|
||||
}
|
||||
},
|
||||
|
||||
+2
-4
@@ -9,7 +9,6 @@
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid var(--vscode-panel-border);
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
@@ -17,12 +16,12 @@
|
||||
background-color: var(--vscode-sideBar-background);
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-right: 1px solid var(--vscode-panel-border);
|
||||
}
|
||||
|
||||
.ai-customization-management-editor .sidebar-content {
|
||||
height: 100%;
|
||||
padding: 12px 0 12px 4px;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -41,7 +40,6 @@
|
||||
padding: 4px 8px;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
margin: 1px 6px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.1s ease, opacity 0.1s ease;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
const openFileItem = (item: IChangesFileItem, items: IChangesFileItem[], sideBySide: boolean) => {
|
||||
const { uri: modifiedFileUri, originalUri, isDeletion } = item;
|
||||
const currentIndex = items.indexOf(item);
|
||||
|
||||
// Ignore folder elements - only open files
|
||||
if (!isChangesFileItem(e.element)) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string | undefined>());
|
||||
readonly onDidChange: Event<string | undefined> = 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<void> {
|
||||
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<IBranchItem> = {
|
||||
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<IBranchItem>(
|
||||
'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<IBranchItem>[] {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { basename, isEqual } from '../../../../base/common/resources.js';
|
||||
import { URI } from '../../../../base/common/uri.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 { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
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';
|
||||
const MAX_RECENT_FOLDERS = 10;
|
||||
const FILTER_THRESHOLD = 10;
|
||||
|
||||
interface IFolderItem {
|
||||
readonly uri: URI;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A folder picker that uses the action widget dropdown to show a list of
|
||||
* recently selected and recently opened folders. Remembers the last selected
|
||||
* folder and recently picked folders in storage. Enables a filter input when
|
||||
* there are more than 10 items.
|
||||
*/
|
||||
export class FolderPicker extends Disposable {
|
||||
|
||||
private readonly _onDidSelectFolder = this._register(new Emitter<URI>());
|
||||
readonly onDidSelectFolder: Event<URI> = this._onDidSelectFolder.event;
|
||||
|
||||
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());
|
||||
|
||||
get selectedFolderUri(): URI | undefined {
|
||||
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,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
// Restore last picked folder
|
||||
const lastFolder = this.storageService.get(STORAGE_KEY_LAST_FOLDER, StorageScope.PROFILE);
|
||||
if (lastFolder) {
|
||||
try { this._selectedFolderUri = URI.parse(lastFolder); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// Restore recently picked folders
|
||||
try {
|
||||
const stored = this.storageService.get(STORAGE_KEY_RECENT_FOLDERS, StorageScope.PROFILE);
|
||||
if (stored) {
|
||||
this._recentlyPickedFolders = JSON.parse(stored).map((s: string) => URI.parse(s));
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
|
||||
// Pre-fetch recently opened folders
|
||||
this.workspacesService.getRecentlyOpened().then(recent => {
|
||||
this._cachedRecentFolders = recent.workspaces
|
||||
.filter(isRecentFolder)
|
||||
.slice(0, MAX_RECENT_FOLDERS)
|
||||
.map(r => ({ uri: r.folderUri, label: r.label }));
|
||||
}).catch(() => { /* ignore */ });
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the folder picker trigger button into the given container.
|
||||
* Returns the container element.
|
||||
*/
|
||||
render(container: HTMLElement): HTMLElement {
|
||||
this._renderDisposables.clear();
|
||||
|
||||
const slot = dom.append(container, dom.$('.sessions-chat-picker-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(trigger);
|
||||
|
||||
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();
|
||||
}
|
||||
}));
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the folder picker dropdown anchored to the trigger element.
|
||||
*/
|
||||
showPicker(): void {
|
||||
if (!this._triggerElement || this.actionWidgetService.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFolderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri;
|
||||
const items = this._buildItems(currentFolderUri);
|
||||
const showFilter = items.filter(i => i.kind === ActionListItemKind.Action).length > FILTER_THRESHOLD;
|
||||
|
||||
const triggerElement = this._triggerElement;
|
||||
const delegate: IActionListDelegate<IFolderItem> = {
|
||||
onSelect: (item) => {
|
||||
this.actionWidgetService.hide();
|
||||
if (item.uri.scheme === 'command' && item.uri.path === 'browse') {
|
||||
this._browseForFolder();
|
||||
} else {
|
||||
this._selectFolder(item.uri);
|
||||
}
|
||||
},
|
||||
onHide: () => { triggerElement.focus(); },
|
||||
};
|
||||
|
||||
this.actionWidgetService.show<IFolderItem>(
|
||||
'folderPicker',
|
||||
false,
|
||||
items,
|
||||
delegate,
|
||||
this._triggerElement,
|
||||
undefined,
|
||||
[],
|
||||
{
|
||||
getAriaLabel: (item) => item.label ?? '',
|
||||
getWidgetAriaLabel: () => localize('folderPicker.ariaLabel', "Folder Picker"),
|
||||
},
|
||||
showFilter ? { showFilter: true, filterPlaceholder: localize('folderPicker.filter', "Filter folders...") } : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically set the selected folder.
|
||||
*/
|
||||
setSelectedFolder(folderUri: URI): void {
|
||||
this._selectFolder(folderUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the selected folder.
|
||||
*/
|
||||
clearSelection(): void {
|
||||
this._selectedFolderUri = undefined;
|
||||
this._updateTriggerLabel(this._triggerElement);
|
||||
}
|
||||
|
||||
private _selectFolder(folderUri: URI): void {
|
||||
this._selectedFolderUri = folderUri;
|
||||
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);
|
||||
}
|
||||
|
||||
private async _browseForFolder(): Promise<void> {
|
||||
try {
|
||||
const selected = await this.fileDialogService.showOpenDialog({
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
title: localize('selectFolder', "Select Folder"),
|
||||
});
|
||||
if (selected?.[0]) {
|
||||
this._selectFolder(selected[0]);
|
||||
}
|
||||
} catch {
|
||||
// dialog was cancelled or failed — nothing to do
|
||||
}
|
||||
}
|
||||
|
||||
private _addToRecentlyPickedFolders(folderUri: URI): void {
|
||||
this._recentlyPickedFolders = [folderUri, ...this._recentlyPickedFolders.filter(f => !isEqual(f, folderUri))].slice(0, MAX_RECENT_FOLDERS);
|
||||
this.storageService.store(STORAGE_KEY_RECENT_FOLDERS, JSON.stringify(this._recentlyPickedFolders.map(f => f.toString())), StorageScope.PROFILE, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private _buildItems(currentFolderUri: URI | undefined): IActionListItem<IFolderItem>[] {
|
||||
const seenUris = new Set<string>();
|
||||
if (currentFolderUri) {
|
||||
seenUris.add(currentFolderUri.toString());
|
||||
}
|
||||
|
||||
const items: IActionListItem<IFolderItem>[] = [];
|
||||
|
||||
// Currently selected folder (shown first, checked)
|
||||
if (currentFolderUri) {
|
||||
items.push({
|
||||
kind: ActionListItemKind.Action,
|
||||
label: basename(currentFolderUri),
|
||||
group: { title: '', icon: Codicon.check },
|
||||
item: { uri: currentFolderUri, label: basename(currentFolderUri) },
|
||||
});
|
||||
}
|
||||
|
||||
// Combine recently picked folders and recently opened folders
|
||||
const allFolders: { uri: URI; label?: string }[] = [
|
||||
...this._recentlyPickedFolders.map(uri => ({ uri })),
|
||||
...this._cachedRecentFolders,
|
||||
];
|
||||
for (const folder of allFolders) {
|
||||
const key = folder.uri.toString();
|
||||
if (seenUris.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seenUris.add(key);
|
||||
const label = folder.label || basename(folder.uri);
|
||||
items.push({
|
||||
kind: ActionListItemKind.Action,
|
||||
label,
|
||||
group: { title: '', icon: Codicon.blank },
|
||||
item: { uri: folder.uri, label },
|
||||
});
|
||||
}
|
||||
|
||||
// Separator + Browse...
|
||||
if (items.length > 0) {
|
||||
items.push({
|
||||
kind: ActionListItemKind.Separator,
|
||||
label: '',
|
||||
});
|
||||
}
|
||||
items.push({
|
||||
kind: ActionListItemKind.Action,
|
||||
label: localize('browseFolder', "Browse..."),
|
||||
group: { title: '', icon: Codicon.folderOpened },
|
||||
item: { uri: URI.from({ scheme: 'command', path: 'browse' }), label: localize('browseFolder', "Browse...") },
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private _updateTriggerLabel(trigger: HTMLElement | undefined): void {
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
dom.clearNode(trigger);
|
||||
const folderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri;
|
||||
const label = folderUri ? basename(folderUri) : localize('pickFolder', "Pick Folder");
|
||||
|
||||
dom.append(trigger, renderIcon(Codicon.folder));
|
||||
const labelSpan = dom.append(trigger, dom.$('span.sessions-chat-dropdown-label'));
|
||||
labelSpan.textContent = label;
|
||||
dom.append(trigger, renderIcon(Codicon.chevronDown));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -7,27 +7,27 @@ import * as dom from '../../../../base/browser/dom.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
|
||||
import { Emitter } from '../../../../base/common/event.js';
|
||||
import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
|
||||
import { isObject } from '../../../../base/common/types.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickItemWithResource } from '../../../../platform/quickinput/common/quickInput.js';
|
||||
import { AnythingQuickAccessProviderRunOptions } from '../../../../platform/quickinput/common/quickAccess.js';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js';
|
||||
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
|
||||
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
|
||||
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
|
||||
import { ILabelService } from '../../../../platform/label/common/label.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { basename } from '../../../../base/common/resources.js';
|
||||
import { Schemas } from '../../../../base/common/network.js';
|
||||
|
||||
import { AnythingQuickAccessProvider } from '../../../../workbench/contrib/search/browser/anythingQuickAccess.js';
|
||||
import { IChatRequestVariableEntry, OmittedState } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
|
||||
import { isSupportedChatFileScheme } from '../../../../workbench/contrib/chat/common/constants.js';
|
||||
import { resizeImage } from '../../../../workbench/contrib/chat/browser/chatImageUtils.js';
|
||||
import { imageToHash, isImage } from '../../../../workbench/contrib/chat/browser/widget/input/editor/chatPasteProviders.js';
|
||||
import { getPathForFile } from '../../../../platform/dnd/browser/dnd.js';
|
||||
import { getExcludes, ISearchConfiguration, ISearchService, QueryType } from '../../../../workbench/services/search/common/search.js';
|
||||
|
||||
/**
|
||||
* Manages context attachments for the sessions new-chat widget.
|
||||
@@ -36,6 +36,7 @@ import { getPathForFile } from '../../../../platform/dnd/browser/dnd.js';
|
||||
* - File picker via quick access ("Files and Open Folders...")
|
||||
* - Image from Clipboard
|
||||
* - Drag and drop files
|
||||
* - Paste images from clipboard (Ctrl/Cmd+V)
|
||||
*/
|
||||
export class NewChatContextAttachments extends Disposable {
|
||||
|
||||
@@ -51,12 +52,14 @@ export class NewChatContextAttachments extends Disposable {
|
||||
}
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@ITextModelService private readonly textModelService: ITextModelService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@ISearchService private readonly searchService: ISearchService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -168,50 +171,241 @@ export class NewChatContextAttachments extends Disposable {
|
||||
}));
|
||||
}
|
||||
|
||||
// --- Paste ---
|
||||
|
||||
registerPasteHandler(element: HTMLElement): void {
|
||||
const supportedMimeTypes = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/bmp',
|
||||
'image/gif',
|
||||
'image/tiff'
|
||||
];
|
||||
|
||||
this._register(dom.addDisposableListener(element, dom.EventType.PASTE, async (e: ClipboardEvent) => {
|
||||
const items = e.clipboardData?.items;
|
||||
if (!items) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check synchronously for image data before any async work
|
||||
// so preventDefault stops the editor from inserting text.
|
||||
let imageFile: File | undefined;
|
||||
for (const item of Array.from(items)) {
|
||||
if (!item.type.startsWith('image/') || !supportedMimeTypes.includes(item.type)) {
|
||||
continue;
|
||||
}
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
imageFile = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!imageFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const arrayBuffer = await imageFile.arrayBuffer();
|
||||
const data = new Uint8Array(arrayBuffer);
|
||||
if (!isImage(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resizedData = await resizeImage(data, imageFile.type);
|
||||
const displayName = this._getUniqueImageName();
|
||||
|
||||
this._addAttachments({
|
||||
id: await imageToHash(resizedData),
|
||||
name: displayName,
|
||||
fullName: displayName,
|
||||
value: resizedData,
|
||||
kind: 'image',
|
||||
});
|
||||
}, true));
|
||||
}
|
||||
|
||||
// --- Picker ---
|
||||
|
||||
showPicker(): void {
|
||||
// Build addition picks for the quick access
|
||||
const additionPicks: IQuickPickItem[] = [];
|
||||
showPicker(folderUri?: URI): void {
|
||||
const picker = this.quickInputService.createQuickPick<IQuickPickItem>({ useSeparators: true });
|
||||
const disposables = new DisposableStore();
|
||||
picker.placeholder = localize('chatContext.attach.placeholder', "Attach as context...");
|
||||
picker.matchOnDescription = true;
|
||||
picker.sortByLabel = false;
|
||||
|
||||
// "Files and Open Folders..." pick - opens a file dialog
|
||||
additionPicks.push({
|
||||
label: localize('filesAndFolders', "Files and Open Folders..."),
|
||||
iconClass: ThemeIcon.asClassName(Codicon.file),
|
||||
id: 'sessions.filesAndFolders',
|
||||
});
|
||||
|
||||
// "Image from Clipboard" pick
|
||||
additionPicks.push({
|
||||
label: localize('imageFromClipboard', "Image from Clipboard"),
|
||||
iconClass: ThemeIcon.asClassName(Codicon.fileMedia),
|
||||
id: 'sessions.imageFromClipboard',
|
||||
});
|
||||
|
||||
const providerOptions: AnythingQuickAccessProviderRunOptions = {
|
||||
filter: (pick) => {
|
||||
if (_isQuickPickItemWithResource(pick) && pick.resource) {
|
||||
return this.instantiationService.invokeFunction(accessor => isSupportedChatFileScheme(accessor, pick.resource!.scheme));
|
||||
}
|
||||
return true;
|
||||
const staticPicks: (IQuickPickItem | IQuickPickSeparator)[] = [
|
||||
{
|
||||
label: localize('files', "Files..."),
|
||||
iconClass: ThemeIcon.asClassName(Codicon.file),
|
||||
id: 'sessions.filesAndFolders',
|
||||
},
|
||||
additionPicks,
|
||||
handleAccept: async (item: IQuickPickItem) => {
|
||||
if (item.id === 'sessions.filesAndFolders') {
|
||||
await this._handleFileDialog();
|
||||
} else if (item.id === 'sessions.imageFromClipboard') {
|
||||
await this._handleClipboardImage();
|
||||
} else {
|
||||
await this._handleFilePick(item as IQuickPickItemWithResource);
|
||||
{
|
||||
label: localize('imageFromClipboard', "Image from Clipboard"),
|
||||
iconClass: ThemeIcon.asClassName(Codicon.fileMedia),
|
||||
id: 'sessions.imageFromClipboard',
|
||||
},
|
||||
];
|
||||
|
||||
picker.items = staticPicks;
|
||||
picker.show();
|
||||
|
||||
if (folderUri) {
|
||||
let searchCts: CancellationTokenSource | undefined;
|
||||
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const runSearch = (filePattern?: string) => {
|
||||
searchCts?.dispose(true);
|
||||
searchCts = new CancellationTokenSource();
|
||||
const token = searchCts.token;
|
||||
|
||||
picker.busy = true;
|
||||
this._collectFilePicks(folderUri, filePattern, token).then(filePicks => {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
picker.busy = false;
|
||||
if (filePicks.length > 0) {
|
||||
picker.items = [
|
||||
...staticPicks,
|
||||
{ type: 'separator', label: basename(folderUri) },
|
||||
...filePicks,
|
||||
];
|
||||
} else {
|
||||
picker.items = staticPicks;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Initial search (no filter)
|
||||
runSearch();
|
||||
|
||||
// Re-search on user input with debounce
|
||||
disposables.add(picker.onDidChangeValue(value => {
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
debounceTimer = setTimeout(() => runSearch(value || undefined), 200);
|
||||
}));
|
||||
|
||||
disposables.add({ dispose: () => { searchCts?.dispose(true); if (debounceTimer) { clearTimeout(debounceTimer); } } });
|
||||
}
|
||||
|
||||
disposables.add(picker.onDidAccept(async () => {
|
||||
const [selected] = picker.selectedItems;
|
||||
if (!selected) {
|
||||
picker.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
picker.hide();
|
||||
|
||||
if (selected.id === 'sessions.filesAndFolders') {
|
||||
await this._handleFileDialog();
|
||||
} else if (selected.id === 'sessions.imageFromClipboard') {
|
||||
await this._handleClipboardImage();
|
||||
} else if (selected.id) {
|
||||
await this._attachFileUri(URI.parse(selected.id), selected.label);
|
||||
}
|
||||
}));
|
||||
|
||||
disposables.add(picker.onDidHide(() => {
|
||||
picker.dispose();
|
||||
disposables.dispose();
|
||||
}));
|
||||
}
|
||||
|
||||
private async _collectFilePicks(rootUri: URI, filePattern?: string, token?: CancellationToken): Promise<IQuickPickItem[]> {
|
||||
const maxFiles = 200;
|
||||
|
||||
// For local file:// URIs, use the search service which respects .gitignore and excludes
|
||||
if (rootUri.scheme === Schemas.file || rootUri.scheme === Schemas.vscodeRemote) {
|
||||
return this._collectFilePicksViaSearch(rootUri, maxFiles, filePattern, token);
|
||||
}
|
||||
|
||||
// For virtual filesystems (e.g. github-remote-file://), walk the tree via IFileService
|
||||
return this._collectFilePicksViaFileService(rootUri, maxFiles, filePattern);
|
||||
}
|
||||
|
||||
private async _collectFilePicksViaSearch(rootUri: URI, maxFiles: number, filePattern?: string, token?: CancellationToken): Promise<IQuickPickItem[]> {
|
||||
const excludePattern = getExcludes(this.configurationService.getValue<ISearchConfiguration>({ resource: rootUri }));
|
||||
|
||||
try {
|
||||
const searchResult = await this.searchService.fileSearch({
|
||||
folderQueries: [{
|
||||
folder: rootUri,
|
||||
disregardIgnoreFiles: false,
|
||||
}],
|
||||
type: QueryType.File,
|
||||
filePattern: filePattern || '',
|
||||
excludePattern,
|
||||
sortByScore: true,
|
||||
maxResults: maxFiles,
|
||||
}, token);
|
||||
|
||||
return searchResult.results.map(result => ({
|
||||
label: basename(result.resource),
|
||||
description: this.labelService.getUriLabel(result.resource, { relative: true }),
|
||||
iconClass: ThemeIcon.asClassName(Codicon.file),
|
||||
id: result.resource.toString(),
|
||||
} satisfies IQuickPickItem));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async _collectFilePicksViaFileService(rootUri: URI, maxFiles: number, filePattern?: string): Promise<IQuickPickItem[]> {
|
||||
const picks: IQuickPickItem[] = [];
|
||||
const patternLower = filePattern?.toLowerCase();
|
||||
const maxDepth = 10;
|
||||
|
||||
const collect = async (uri: URI, depth: number): Promise<void> => {
|
||||
if (picks.length >= maxFiles || depth > maxDepth) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const stat = await this.fileService.resolve(uri);
|
||||
if (!stat.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
const children = stat.children.slice().sort((a, b) => {
|
||||
if (a.isDirectory !== b.isDirectory) {
|
||||
return a.isDirectory ? -1 : 1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
for (const child of children) {
|
||||
if (picks.length >= maxFiles) {
|
||||
break;
|
||||
}
|
||||
if (child.isDirectory) {
|
||||
await collect(child.resource, depth + 1);
|
||||
} else {
|
||||
if (patternLower && !child.name.toLowerCase().includes(patternLower)) {
|
||||
continue;
|
||||
}
|
||||
picks.push({
|
||||
label: child.name,
|
||||
description: this.labelService.getUriLabel(child.resource, { relative: true }),
|
||||
iconClass: ThemeIcon.asClassName(Codicon.file),
|
||||
id: child.resource.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore errors for individual directories
|
||||
}
|
||||
};
|
||||
|
||||
this.quickInputService.quickAccess.show('', {
|
||||
enabledProviderPrefixes: [AnythingQuickAccessProvider.PREFIX],
|
||||
placeholder: localize('chatContext.attach.placeholder', "Attach as context..."),
|
||||
providerOptions,
|
||||
});
|
||||
await collect(rootUri, 0);
|
||||
return picks;
|
||||
}
|
||||
|
||||
private async _handleFileDialog(): Promise<void> {
|
||||
@@ -230,13 +424,6 @@ export class NewChatContextAttachments extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleFilePick(pick: IQuickPickItemWithResource): Promise<void> {
|
||||
if (!pick.resource) {
|
||||
return;
|
||||
}
|
||||
await this._attachFileUri(pick.resource, pick.label);
|
||||
}
|
||||
|
||||
private async _attachFileUri(uri: URI, name: string): Promise<void> {
|
||||
if (/\.(png|jpg|jpeg|bmp|gif|tiff)$/i.test(uri.path)) {
|
||||
const readFile = await this.fileService.readFile(uri);
|
||||
@@ -274,10 +461,12 @@ export class NewChatContextAttachments extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const displayName = this._getUniqueImageName();
|
||||
|
||||
this._addAttachments({
|
||||
id: await imageToHash(imageData),
|
||||
name: localize('pastedImage', "Pasted Image"),
|
||||
fullName: localize('pastedImage', "Pasted Image"),
|
||||
name: displayName,
|
||||
fullName: displayName,
|
||||
value: imageData,
|
||||
kind: 'image',
|
||||
});
|
||||
@@ -285,6 +474,15 @@ export class NewChatContextAttachments extends Disposable {
|
||||
|
||||
// --- State management ---
|
||||
|
||||
private _getUniqueImageName(): string {
|
||||
const baseName = localize('pastedImage', "Pasted Image");
|
||||
let name = baseName;
|
||||
for (let i = 2; this._attachedContext.some(a => a.name === name); i++) {
|
||||
name = `${baseName} ${i}`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private _addAttachments(...entries: IChatRequestVariableEntry[]): void {
|
||||
for (const entry of entries) {
|
||||
if (!this._attachedContext.some(e => e.id === entry.id)) {
|
||||
@@ -310,9 +508,3 @@ export class NewChatContextAttachments extends Disposable {
|
||||
this._onDidChangeContext.fire();
|
||||
}
|
||||
}
|
||||
|
||||
function _isQuickPickItemWithResource(obj: unknown): obj is IQuickPickItemWithResource {
|
||||
return (
|
||||
isObject(obj)
|
||||
&& URI.isUri((obj as IQuickPickItemWithResource).resource));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<string, IChatSessionProviderOptionItem>;
|
||||
readonly onDidChange: Event<NewSessionChangeType>;
|
||||
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<NewSessionChangeType>());
|
||||
readonly onDidChange: Event<NewSessionChangeType> = this._onDidChange.event;
|
||||
|
||||
readonly target = AgentSessionProviders.Background;
|
||||
readonly selectedOptions = new Map<string, IChatSessionProviderOptionItem>();
|
||||
|
||||
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<NewSessionChangeType>());
|
||||
readonly onDidChange: Event<NewSessionChangeType> = this._onDidChange.event;
|
||||
|
||||
readonly selectedOptions = new Map<string, IChatSessionProviderOptionItem>();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
});
|
||||
|
||||
@@ -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<AgentSessionProviders>());
|
||||
readonly onDidChangeTarget: Event<AgentSessionProviders> = 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<IsolationMode>());
|
||||
readonly onDidChange: Event<IsolationMode> = 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
|
||||
@@ -36,7 +36,8 @@ Registry.as<IConfigurationRegistry>(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',
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
|
||||
import { GitHubFileSystemProvider, GITHUB_REMOTE_FILE_SCHEME } from './githubFileSystemProvider.js';
|
||||
|
||||
// --- View registration is currently disabled in favor of the "Add Context" picker.
|
||||
// The Files view will be re-enabled once we finalize the sessions auxiliary bar layout.
|
||||
|
||||
// --- Session Repo FileSystem Provider Registration
|
||||
|
||||
class GitHubFileSystemProviderContribution extends Disposable {
|
||||
|
||||
static readonly ID = 'workbench.contrib.githubFileSystemProvider';
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
const provider = this._register(instantiationService.createInstance(GitHubFileSystemProvider));
|
||||
this._register(fileService.registerProvider(GITHUB_REMOTE_FILE_SCHEME, provider));
|
||||
}
|
||||
}
|
||||
|
||||
registerWorkbenchContribution2(
|
||||
GitHubFileSystemProviderContribution.ID,
|
||||
GitHubFileSystemProviderContribution,
|
||||
WorkbenchPhase.AfterRestored
|
||||
);
|
||||
@@ -0,0 +1,577 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './media/fileTreeView.css';
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { IAsyncDataSource, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';
|
||||
import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js';
|
||||
import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js';
|
||||
import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { autorun, derived, IObservable, observableFromEvent } from '../../../../base/common/observable.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { FileKind, IFileService, IFileStat } from '../../../../platform/files/common/files.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
|
||||
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
|
||||
import { ILabelService } from '../../../../platform/label/common/label.js';
|
||||
import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js';
|
||||
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
|
||||
import { IResourceLabel, ResourceLabels } from '../../../../workbench/browser/labels.js';
|
||||
import { IViewPaneOptions, ViewPane } from '../../../../workbench/browser/parts/views/viewPane.js';
|
||||
import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/viewPaneContainer.js';
|
||||
import { IViewDescriptorService } from '../../../../workbench/common/views.js';
|
||||
import { createFileIconThemableTreeContainerScope } from '../../../../workbench/contrib/files/browser/views/explorerView.js';
|
||||
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
|
||||
import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js';
|
||||
import { getChatSessionType } from '../../../../workbench/contrib/chat/common/model/chatUri.js';
|
||||
import { isIChatSessionFileChange2 } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
|
||||
import { ACTIVE_GROUP, IEditorService, 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 { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { IStorageService } from '../../../../platform/storage/common/storage.js';
|
||||
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { ISessionsManagementService, IActiveSessionItem } from '../../sessions/browser/sessionsManagementService.js';
|
||||
import { GITHUB_REMOTE_FILE_SCHEME } from './githubFileSystemProvider.js';
|
||||
import { basename } from '../../../../base/common/path.js';
|
||||
import { isEqual } from '../../../../base/common/resources.js';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
// --- Constants
|
||||
|
||||
export const FILE_TREE_VIEW_CONTAINER_ID = 'workbench.view.agentSessions.fileTreeContainer';
|
||||
export const FILE_TREE_VIEW_ID = 'workbench.view.agentSessions.fileTree';
|
||||
|
||||
// --- Tree Item
|
||||
|
||||
interface IFileTreeItem {
|
||||
readonly uri: URI;
|
||||
readonly name: string;
|
||||
readonly isDirectory: boolean;
|
||||
}
|
||||
|
||||
// --- Data Source
|
||||
|
||||
class FileTreeDataSource implements IAsyncDataSource<URI, IFileTreeItem> {
|
||||
|
||||
constructor(
|
||||
private readonly fileService: IFileService,
|
||||
private readonly logService: ILogService,
|
||||
) { }
|
||||
|
||||
hasChildren(element: URI | IFileTreeItem): boolean {
|
||||
if (URI.isUri(element)) {
|
||||
return true; // root
|
||||
}
|
||||
return element.isDirectory;
|
||||
}
|
||||
|
||||
async getChildren(element: URI | IFileTreeItem): Promise<IFileTreeItem[]> {
|
||||
const uri = URI.isUri(element) ? element : element.uri;
|
||||
|
||||
try {
|
||||
const stat = await this.fileService.resolve(uri);
|
||||
if (!stat.children) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return stat.children
|
||||
.map((child: IFileStat): IFileTreeItem => ({
|
||||
uri: child.resource,
|
||||
name: child.name,
|
||||
isDirectory: child.isDirectory,
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
// Directories first, then alphabetical
|
||||
if (a.isDirectory !== b.isDirectory) {
|
||||
return a.isDirectory ? -1 : 1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
} catch (e) {
|
||||
this.logService.warn(`[FileTreeView] Error fetching children for ${uri.toString()}:`, e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Delegate
|
||||
|
||||
class FileTreeDelegate implements IListVirtualDelegate<IFileTreeItem> {
|
||||
getHeight(): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(): string {
|
||||
return FileTreeRenderer.TEMPLATE_ID;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Renderer
|
||||
|
||||
interface IFileTreeTemplate {
|
||||
readonly label: IResourceLabel;
|
||||
readonly templateDisposables: DisposableStore;
|
||||
}
|
||||
|
||||
class FileTreeRenderer implements ICompressibleTreeRenderer<IFileTreeItem, void, IFileTreeTemplate> {
|
||||
static readonly TEMPLATE_ID = 'fileTreeRenderer';
|
||||
readonly templateId = FileTreeRenderer.TEMPLATE_ID;
|
||||
|
||||
constructor(
|
||||
private readonly labels: ResourceLabels,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
) { }
|
||||
|
||||
renderTemplate(container: HTMLElement): IFileTreeTemplate {
|
||||
const templateDisposables = new DisposableStore();
|
||||
const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true, supportIcons: true }));
|
||||
return { label, templateDisposables };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IFileTreeItem, void>, _index: number, templateData: IFileTreeTemplate): void {
|
||||
const element = node.element;
|
||||
templateData.label.element.style.display = 'flex';
|
||||
templateData.label.setFile(element.uri, {
|
||||
fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE,
|
||||
hidePath: true,
|
||||
});
|
||||
}
|
||||
|
||||
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IFileTreeItem>, void>, _index: number, templateData: IFileTreeTemplate): void {
|
||||
const compressed = node.element;
|
||||
const lastElement = compressed.elements[compressed.elements.length - 1];
|
||||
|
||||
templateData.label.element.style.display = 'flex';
|
||||
|
||||
const label = compressed.elements.map(e => e.name);
|
||||
templateData.label.setResource({ resource: lastElement.uri, name: label }, {
|
||||
fileKind: lastElement.isDirectory ? FileKind.FOLDER : FileKind.FILE,
|
||||
separator: this.labelService.getSeparator(lastElement.uri.scheme),
|
||||
});
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IFileTreeTemplate): void {
|
||||
templateData.templateDisposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Compression Delegate
|
||||
|
||||
class FileTreeCompressionDelegate {
|
||||
isIncompressible(element: IFileTreeItem): boolean {
|
||||
return !element.isDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
// --- View Pane
|
||||
|
||||
export class FileTreeViewPane extends ViewPane {
|
||||
|
||||
private bodyContainer: HTMLElement | undefined;
|
||||
private welcomeContainer: HTMLElement | undefined;
|
||||
private treeContainer: HTMLElement | undefined;
|
||||
|
||||
private tree: WorkbenchCompressibleAsyncDataTree<URI, IFileTreeItem> | undefined;
|
||||
|
||||
private readonly renderDisposables = this._register(new DisposableStore());
|
||||
private readonly treeInputDisposable = this._register(new MutableDisposable());
|
||||
|
||||
private currentBodyHeight = 0;
|
||||
private currentBodyWidth = 0;
|
||||
|
||||
/**
|
||||
* Observable that tracks the root URI for the file tree.
|
||||
* - For background sessions: the worktree or repository local path
|
||||
* - For cloud sessions: a github-remote-file:// URI derived from the session's repository metadata
|
||||
* - For local sessions: the workspace folder
|
||||
*/
|
||||
private readonly treeRootUri: IObservable<URI | undefined>;
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IHoverService hoverService: IHoverService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@ISessionsManagementService private readonly sessionManagementService: ISessionsManagementService,
|
||||
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
|
||||
|
||||
// Track active session changes AND session model updates (metadata/changes can arrive later)
|
||||
const sessionsChangedSignal = observableFromEvent(
|
||||
this,
|
||||
this.agentSessionsService.model.onDidChangeSessions,
|
||||
() => ({}),
|
||||
);
|
||||
|
||||
this.treeRootUri = derived(reader => {
|
||||
const activeSession = this.sessionManagementService.activeSession.read(reader);
|
||||
sessionsChangedSignal.read(reader); // re-evaluate when sessions data updates
|
||||
return this.resolveTreeRoot(activeSession);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the root URI for the file tree based on the active session type.
|
||||
* Tries multiple data sources: IActiveSessionItem fields, agent session model metadata,
|
||||
* and file change URIs as a last resort.
|
||||
*/
|
||||
private resolveTreeRoot(activeSession: IActiveSessionItem | undefined): URI | undefined {
|
||||
if (!activeSession) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sessionType = getChatSessionType(activeSession.resource);
|
||||
|
||||
// 1. Try the direct worktree/repository fields from IActiveSessionItem
|
||||
if (activeSession.worktree) {
|
||||
this.logService.info(`[FileTreeView] Using worktree: ${activeSession.worktree.toString()}`);
|
||||
return activeSession.worktree;
|
||||
}
|
||||
if (activeSession.repository && activeSession.repository.scheme === 'file') {
|
||||
this.logService.info(`[FileTreeView] Using repository: ${activeSession.repository.toString()}`);
|
||||
return activeSession.repository;
|
||||
}
|
||||
|
||||
// 2. Query the agent session model directly for metadata
|
||||
const agentSession = this.agentSessionsService.getSession(activeSession.resource);
|
||||
if (agentSession?.metadata) {
|
||||
const metadata = agentSession.metadata;
|
||||
|
||||
// Background sessions: local paths (try multiple known metadata keys)
|
||||
const workingDir = metadata.workingDirectoryPath as string | undefined;
|
||||
if (workingDir) {
|
||||
this.logService.info(`[FileTreeView] Using metadata.workingDirectoryPath: ${workingDir}`);
|
||||
return URI.file(workingDir);
|
||||
}
|
||||
const worktreePath = metadata.worktreePath as string | undefined;
|
||||
if (worktreePath) {
|
||||
this.logService.info(`[FileTreeView] Using metadata.worktreePath: ${worktreePath}`);
|
||||
return URI.file(worktreePath);
|
||||
}
|
||||
const repositoryPath = metadata.repositoryPath as string | undefined;
|
||||
if (repositoryPath) {
|
||||
this.logService.info(`[FileTreeView] Using metadata.repositoryPath: ${repositoryPath}`);
|
||||
return URI.file(repositoryPath);
|
||||
}
|
||||
|
||||
// Cloud sessions: GitHub repo info in metadata
|
||||
const repoUri = this.extractRepoUriFromMetadata(metadata);
|
||||
if (repoUri) {
|
||||
return repoUri;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. For cloud/remote sessions: try to infer repo from file change URIs
|
||||
if (sessionType === AgentSessionProviders.Cloud || sessionType === AgentSessionProviders.Codex) {
|
||||
const repoUri = this.inferRepoFromChanges(activeSession.resource);
|
||||
if (repoUri) {
|
||||
this.logService.info(`[FileTreeView] Inferred repo from changes: ${repoUri.toString()}`);
|
||||
return repoUri;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Try to parse the repository URI as a GitHub URL
|
||||
if (activeSession.repository) {
|
||||
const repoStr = activeSession.repository.toString();
|
||||
const parsed = this.parseGitHubUrl(repoStr);
|
||||
if (parsed) {
|
||||
this.logService.info(`[FileTreeView] Parsed repository URI as GitHub: ${parsed.owner}/${parsed.repo}`);
|
||||
return URI.from({
|
||||
scheme: GITHUB_REMOTE_FILE_SCHEME,
|
||||
authority: 'github',
|
||||
path: `/${parsed.owner}/${parsed.repo}/HEAD`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.logService.trace(`[FileTreeView] No tree root resolved for session ${activeSession.resource.toString()} (type: ${sessionType})`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a github-remote-file:// URI from session metadata, trying various known fields.
|
||||
*/
|
||||
private extractRepoUriFromMetadata(metadata: { readonly [key: string]: unknown }): URI | undefined {
|
||||
// repositoryNwo: "owner/repo"
|
||||
const repositoryNwo = metadata.repositoryNwo as string | undefined;
|
||||
if (repositoryNwo && repositoryNwo.includes('/')) {
|
||||
this.logService.info(`[FileTreeView] Using metadata.repositoryNwo: ${repositoryNwo}`);
|
||||
return URI.from({
|
||||
scheme: GITHUB_REMOTE_FILE_SCHEME,
|
||||
authority: 'github',
|
||||
path: `/${repositoryNwo}/HEAD`,
|
||||
});
|
||||
}
|
||||
|
||||
// repositoryUrl: "https://github.com/owner/repo"
|
||||
const repositoryUrl = metadata.repositoryUrl as string | undefined;
|
||||
if (repositoryUrl) {
|
||||
const parsed = this.parseGitHubUrl(repositoryUrl);
|
||||
if (parsed) {
|
||||
this.logService.info(`[FileTreeView] Using metadata.repositoryUrl: ${repositoryUrl}`);
|
||||
return URI.from({
|
||||
scheme: GITHUB_REMOTE_FILE_SCHEME,
|
||||
authority: 'github',
|
||||
path: `/${parsed.owner}/${parsed.repo}/HEAD`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// repository: could be "owner/repo" or a URL
|
||||
const repository = metadata.repository as string | undefined;
|
||||
if (repository) {
|
||||
if (repository.includes('/') && !repository.includes(':')) {
|
||||
// Looks like "owner/repo"
|
||||
this.logService.info(`[FileTreeView] Using metadata.repository as nwo: ${repository}`);
|
||||
return URI.from({
|
||||
scheme: GITHUB_REMOTE_FILE_SCHEME,
|
||||
authority: 'github',
|
||||
path: `/${repository}/HEAD`,
|
||||
});
|
||||
}
|
||||
const parsed = this.parseGitHubUrl(repository);
|
||||
if (parsed) {
|
||||
this.logService.info(`[FileTreeView] Using metadata.repository as URL: ${repository}`);
|
||||
return URI.from({
|
||||
scheme: GITHUB_REMOTE_FILE_SCHEME,
|
||||
authority: 'github',
|
||||
path: `/${parsed.owner}/${parsed.repo}/HEAD`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to infer the repository from the session's file change URIs.
|
||||
* Cloud sessions have changes with URIs that reveal the repository.
|
||||
*/
|
||||
private inferRepoFromChanges(sessionResource: URI): URI | undefined {
|
||||
const agentSession = this.agentSessionsService.getSession(sessionResource);
|
||||
if (!agentSession?.changes || !(agentSession.changes instanceof Array)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const change of agentSession.changes) {
|
||||
const fileUri = isIChatSessionFileChange2(change)
|
||||
? (change.modifiedUri ?? change.uri)
|
||||
: change.modifiedUri;
|
||||
|
||||
if (!fileUri) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parsed = this.parseRepoFromFileUri(fileUri);
|
||||
if (parsed) {
|
||||
return URI.from({
|
||||
scheme: GITHUB_REMOTE_FILE_SCHEME,
|
||||
authority: 'github',
|
||||
path: `/${parsed.owner}/${parsed.repo}/${parsed.ref}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to extract GitHub owner/repo from a file change URI.
|
||||
* Handles various URI formats used by cloud sessions.
|
||||
*/
|
||||
private parseRepoFromFileUri(uri: URI): { owner: string; repo: string; ref: string } | undefined {
|
||||
// Pattern: vscode-vfs://github/{owner}/{repo}/...
|
||||
if (uri.authority === 'github' || uri.authority?.startsWith('github')) {
|
||||
const parts = uri.path.split('/').filter(Boolean);
|
||||
if (parts.length >= 2) {
|
||||
return { owner: parts[0], repo: parts[1], ref: 'HEAD' };
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern: github://{owner}/{repo}/... or github1s://{owner}/{repo}/...
|
||||
if (uri.scheme === 'github' || uri.scheme === 'github1s') {
|
||||
const parts = uri.authority ? uri.authority.split('/') : uri.path.split('/').filter(Boolean);
|
||||
if (parts.length >= 2) {
|
||||
return { owner: parts[0], repo: parts[1], ref: 'HEAD' };
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern: https://github.com/{owner}/{repo}/...
|
||||
return this.parseGitHubUrl(uri.toString());
|
||||
}
|
||||
|
||||
private parseGitHubUrl(url: string): { owner: string; repo: string; ref: string } | undefined {
|
||||
const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/i.exec(url)
|
||||
|| /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i.exec(url);
|
||||
return match ? { owner: match[1], repo: match[2], ref: 'HEAD' } : undefined;
|
||||
}
|
||||
|
||||
protected override renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
this.bodyContainer = dom.append(container, $('.file-tree-view-body'));
|
||||
|
||||
// Welcome message for empty state
|
||||
this.welcomeContainer = dom.append(this.bodyContainer, $('.file-tree-welcome'));
|
||||
const welcomeIcon = dom.append(this.welcomeContainer, $('.file-tree-welcome-icon'));
|
||||
welcomeIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.repoClone));
|
||||
const welcomeMessage = dom.append(this.welcomeContainer, $('.file-tree-welcome-message'));
|
||||
welcomeMessage.textContent = localize('fileTreeView.noRepository', "No repository available for this session.");
|
||||
|
||||
// Tree container
|
||||
this.treeContainer = dom.append(this.bodyContainer, $('.file-tree-container.show-file-icons'));
|
||||
this._register(createFileIconThemableTreeContainerScope(this.treeContainer, this.themeService));
|
||||
|
||||
this._register(this.onDidChangeBodyVisibility(visible => {
|
||||
if (visible) {
|
||||
this.onVisible();
|
||||
} else {
|
||||
this.renderDisposables.clear();
|
||||
}
|
||||
}));
|
||||
|
||||
if (this.isBodyVisible()) {
|
||||
this.onVisible();
|
||||
}
|
||||
}
|
||||
|
||||
private onVisible(): void {
|
||||
this.renderDisposables.clear();
|
||||
this.logService.info('[FileTreeView] onVisible called');
|
||||
|
||||
// Create tree if needed
|
||||
if (!this.tree && this.treeContainer) {
|
||||
const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }));
|
||||
const dataSource = new FileTreeDataSource(this.fileService, this.logService);
|
||||
|
||||
this.tree = this.instantiationService.createInstance(
|
||||
WorkbenchCompressibleAsyncDataTree<URI, IFileTreeItem>,
|
||||
'FileTreeView',
|
||||
this.treeContainer,
|
||||
new FileTreeDelegate(),
|
||||
new FileTreeCompressionDelegate(),
|
||||
[this.instantiationService.createInstance(FileTreeRenderer, resourceLabels)],
|
||||
dataSource,
|
||||
{
|
||||
accessibilityProvider: {
|
||||
getAriaLabel: (element: IFileTreeItem) => element.name,
|
||||
getWidgetAriaLabel: () => localize('fileTreeView', "File Tree")
|
||||
},
|
||||
identityProvider: {
|
||||
getId: (element: IFileTreeItem) => element.uri.toString()
|
||||
},
|
||||
compressionEnabled: true,
|
||||
collapseByDefault: (_e: IFileTreeItem) => true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handle tree open events (open files in editor)
|
||||
if (this.tree) {
|
||||
this.renderDisposables.add(this.tree.onDidOpen(async (e) => {
|
||||
if (!e.element || e.element.isDirectory) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.editorService.openEditor({
|
||||
resource: e.element.uri,
|
||||
options: e.editorOptions,
|
||||
}, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
|
||||
}));
|
||||
}
|
||||
|
||||
// React to active session changes
|
||||
let lastRootUri: URI | undefined;
|
||||
this.renderDisposables.add(autorun(reader => {
|
||||
const rootUri = this.treeRootUri.read(reader);
|
||||
const hasRoot = !!rootUri;
|
||||
|
||||
dom.setVisibility(hasRoot, this.treeContainer!);
|
||||
dom.setVisibility(!hasRoot, this.welcomeContainer!);
|
||||
|
||||
if (this.tree && rootUri && !isEqual(rootUri, lastRootUri)) {
|
||||
lastRootUri = rootUri;
|
||||
this.updateTitle(basename(rootUri.path) || rootUri.toString());
|
||||
this.treeInputDisposable.clear();
|
||||
this.tree.setInput(rootUri).then(() => {
|
||||
this.layoutTree();
|
||||
});
|
||||
} else if (!rootUri && lastRootUri) {
|
||||
lastRootUri = undefined;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private layoutTree(): void {
|
||||
if (!this.tree) {
|
||||
return;
|
||||
}
|
||||
this.tree.layout(this.currentBodyHeight, this.currentBodyWidth);
|
||||
}
|
||||
|
||||
protected override layoutBody(height: number, width: number): void {
|
||||
super.layoutBody(height, width);
|
||||
this.currentBodyHeight = height;
|
||||
this.currentBodyWidth = width;
|
||||
this.layoutTree();
|
||||
}
|
||||
|
||||
override focus(): void {
|
||||
super.focus();
|
||||
this.tree?.domFocus();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.tree?.dispose();
|
||||
this.tree = undefined;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// --- View Pane Container
|
||||
|
||||
export class FileTreeViewPaneContainer extends ViewPaneContainer {
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@ILogService logService: ILogService,
|
||||
) {
|
||||
super(FILE_TREE_VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, logService);
|
||||
}
|
||||
|
||||
override create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
parent.classList.add('file-tree-viewlet');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileDeleteOptions, IFileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability, IFileWriteOptions, IStat, createFileSystemProviderError, IFileChange } from '../../../../platform/files/common/files.js';
|
||||
import { IRequestService, asJson } from '../../../../platform/request/common/request.js';
|
||||
import { IAuthenticationService } from '../../../../workbench/services/authentication/common/authentication.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
|
||||
export const GITHUB_REMOTE_FILE_SCHEME = 'github-remote-file';
|
||||
|
||||
/**
|
||||
* GitHub REST API response for the Trees endpoint.
|
||||
* GET /repos/{owner}/{repo}/git/trees/{tree_sha}?recursive=1
|
||||
*/
|
||||
interface IGitHubTreeResponse {
|
||||
readonly sha: string;
|
||||
readonly url: string;
|
||||
readonly truncated: boolean;
|
||||
readonly tree: readonly IGitHubTreeEntry[];
|
||||
}
|
||||
|
||||
interface IGitHubTreeEntry {
|
||||
readonly path: string;
|
||||
readonly mode: string;
|
||||
readonly type: 'blob' | 'tree';
|
||||
readonly sha: string;
|
||||
readonly size?: number;
|
||||
readonly url: string;
|
||||
}
|
||||
|
||||
interface ITreeCacheEntry {
|
||||
/** Map from path → entry metadata */
|
||||
readonly entries: Map<string, { type: FileType; size: number; sha: string }>;
|
||||
readonly fetchedAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A readonly virtual filesystem provider backed by the GitHub REST API.
|
||||
*
|
||||
* URI format: github-remote-file://github/{owner}/{repo}/{ref}/{path...}
|
||||
*
|
||||
* For example: github-remote-file://github/microsoft/vscode/main/src/vs/base/common/uri.ts
|
||||
*
|
||||
* This provider fetches the full recursive tree from the GitHub Trees API on first
|
||||
* access and caches it. Individual file contents are fetched on demand via the
|
||||
* Blobs API.
|
||||
*/
|
||||
export class GitHubFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
|
||||
|
||||
private readonly _onDidChangeCapabilities = this._register(new Emitter<void>());
|
||||
readonly onDidChangeCapabilities: Event<void> = this._onDidChangeCapabilities.event;
|
||||
|
||||
readonly capabilities: FileSystemProviderCapabilities =
|
||||
FileSystemProviderCapabilities.Readonly |
|
||||
FileSystemProviderCapabilities.FileReadWrite |
|
||||
FileSystemProviderCapabilities.PathCaseSensitive;
|
||||
|
||||
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
|
||||
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
|
||||
|
||||
/** Cache keyed by "owner/repo/ref" */
|
||||
private readonly treeCache = new Map<string, ITreeCacheEntry>();
|
||||
|
||||
/** Cache TTL - 5 minutes */
|
||||
private static readonly CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
constructor(
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
// --- URI parsing
|
||||
|
||||
/**
|
||||
* Parse a github-remote-file URI into its components.
|
||||
* Format: github-remote-file://github/{owner}/{repo}/{ref}/{path...}
|
||||
*/
|
||||
private parseUri(resource: URI): { owner: string; repo: string; ref: string; path: string } {
|
||||
// authority = "github"
|
||||
// path = /{owner}/{repo}/{ref}/{rest...}
|
||||
const parts = resource.path.split('/').filter(Boolean);
|
||||
if (parts.length < 3) {
|
||||
throw createFileSystemProviderError('Invalid github-remote-file URI: expected /{owner}/{repo}/{ref}/...', FileSystemProviderErrorCode.FileNotFound);
|
||||
}
|
||||
|
||||
const owner = parts[0];
|
||||
const repo = parts[1];
|
||||
const ref = parts[2];
|
||||
const path = parts.slice(3).join('/');
|
||||
|
||||
return { owner, repo, ref, path };
|
||||
}
|
||||
|
||||
private getCacheKey(owner: string, repo: string, ref: string): string {
|
||||
return `${owner}/${repo}/${ref}`;
|
||||
}
|
||||
|
||||
// --- GitHub API
|
||||
|
||||
private async getAuthToken(): Promise<string> {
|
||||
const sessions = await this.authenticationService.getSessions('github', ['repo']);
|
||||
if (sessions.length > 0) {
|
||||
return sessions[0].accessToken;
|
||||
}
|
||||
|
||||
// Try to create a session if none exists
|
||||
const session = await this.authenticationService.createSession('github', ['repo']);
|
||||
return session.accessToken;
|
||||
}
|
||||
|
||||
private async fetchTree(owner: string, repo: string, ref: string): Promise<ITreeCacheEntry> {
|
||||
const cacheKey = this.getCacheKey(owner, repo, ref);
|
||||
const cached = this.treeCache.get(cacheKey);
|
||||
if (cached && (Date.now() - cached.fetchedAt) < GitHubFileSystemProvider.CACHE_TTL_MS) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
this.logService.info(`[SessionRepoFS] Fetching tree for ${owner}/${repo}@${ref}`);
|
||||
const token = await this.getAuthToken();
|
||||
|
||||
const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/trees/${encodeURIComponent(ref)}?recursive=1`;
|
||||
const response = await this.requestService.request({
|
||||
type: 'GET',
|
||||
url,
|
||||
headers: {
|
||||
'Authorization': `token ${token}`,
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'VSCode-SessionRepoFS',
|
||||
},
|
||||
}, CancellationToken.None);
|
||||
|
||||
const data = await asJson<IGitHubTreeResponse>(response);
|
||||
if (!data) {
|
||||
throw createFileSystemProviderError(`Failed to fetch tree for ${owner}/${repo}@${ref}`, FileSystemProviderErrorCode.Unavailable);
|
||||
}
|
||||
|
||||
const entries = new Map<string, { type: FileType; size: number; sha: string }>();
|
||||
|
||||
// Add root directory entry
|
||||
entries.set('', { type: FileType.Directory, size: 0, sha: data.sha });
|
||||
|
||||
// Track directories implicitly from paths
|
||||
const dirs = new Set<string>();
|
||||
|
||||
for (const entry of data.tree) {
|
||||
const fileType = entry.type === 'tree' ? FileType.Directory : FileType.File;
|
||||
entries.set(entry.path, { type: fileType, size: entry.size ?? 0, sha: entry.sha });
|
||||
|
||||
if (fileType === FileType.Directory) {
|
||||
dirs.add(entry.path);
|
||||
}
|
||||
|
||||
// Ensure parent directories are tracked
|
||||
const pathParts = entry.path.split('/');
|
||||
for (let i = 1; i < pathParts.length; i++) {
|
||||
const parentPath = pathParts.slice(0, i).join('/');
|
||||
if (!dirs.has(parentPath)) {
|
||||
dirs.add(parentPath);
|
||||
if (!entries.has(parentPath)) {
|
||||
entries.set(parentPath, { type: FileType.Directory, size: 0, sha: '' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cacheEntry: ITreeCacheEntry = { entries, fetchedAt: Date.now() };
|
||||
this.treeCache.set(cacheKey, cacheEntry);
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
// --- IFileSystemProvider
|
||||
|
||||
async stat(resource: URI): Promise<IStat> {
|
||||
const { owner, repo, ref, path } = this.parseUri(resource);
|
||||
const tree = await this.fetchTree(owner, repo, ref);
|
||||
const entry = tree.entries.get(path);
|
||||
|
||||
if (!entry) {
|
||||
throw createFileSystemProviderError('File not found', FileSystemProviderErrorCode.FileNotFound);
|
||||
}
|
||||
|
||||
return {
|
||||
type: entry.type,
|
||||
ctime: 0,
|
||||
mtime: 0,
|
||||
size: entry.size,
|
||||
};
|
||||
}
|
||||
|
||||
async readdir(resource: URI): Promise<[string, FileType][]> {
|
||||
const { owner, repo, ref, path } = this.parseUri(resource);
|
||||
const tree = await this.fetchTree(owner, repo, ref);
|
||||
|
||||
const prefix = path ? path + '/' : '';
|
||||
const result: [string, FileType][] = [];
|
||||
|
||||
for (const [entryPath, entry] of tree.entries) {
|
||||
if (!entryPath.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relativePath = entryPath.slice(prefix.length);
|
||||
// Only include direct children (no nested paths)
|
||||
if (relativePath && !relativePath.includes('/')) {
|
||||
result.push([relativePath, entry.type]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async readFile(resource: URI): Promise<Uint8Array> {
|
||||
const { owner, repo, ref, path } = this.parseUri(resource);
|
||||
const tree = await this.fetchTree(owner, repo, ref);
|
||||
const entry = tree.entries.get(path);
|
||||
|
||||
if (!entry || entry.type === FileType.Directory) {
|
||||
throw createFileSystemProviderError('File not found', FileSystemProviderErrorCode.FileNotFound);
|
||||
}
|
||||
|
||||
const token = await this.getAuthToken();
|
||||
|
||||
// Fetch file content via the Blobs API
|
||||
const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/blobs/${encodeURIComponent(entry.sha)}`;
|
||||
const response = await this.requestService.request({
|
||||
type: 'GET',
|
||||
url,
|
||||
headers: {
|
||||
'Authorization': `token ${token}`,
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'VSCode-SessionRepoFS',
|
||||
},
|
||||
}, CancellationToken.None);
|
||||
|
||||
const data = await asJson<{ content: string; encoding: string }>(response);
|
||||
if (!data) {
|
||||
throw createFileSystemProviderError(`Failed to read file ${path}`, FileSystemProviderErrorCode.Unavailable);
|
||||
}
|
||||
|
||||
if (data.encoding === 'base64') {
|
||||
const binaryString = atob(data.content.replace(/\n/g, ''));
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
return new TextEncoder().encode(data.content);
|
||||
}
|
||||
|
||||
// --- Readonly stubs
|
||||
|
||||
watch(): IDisposable {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
async writeFile(_resource: URI, _content: Uint8Array, _opts: IFileWriteOptions): Promise<void> {
|
||||
throw createFileSystemProviderError('Operation not supported', FileSystemProviderErrorCode.NoPermissions);
|
||||
}
|
||||
|
||||
async mkdir(_resource: URI): Promise<void> {
|
||||
throw createFileSystemProviderError('Operation not supported', FileSystemProviderErrorCode.NoPermissions);
|
||||
}
|
||||
|
||||
async delete(_resource: URI, _opts: IFileDeleteOptions): Promise<void> {
|
||||
throw createFileSystemProviderError('Operation not supported', FileSystemProviderErrorCode.NoPermissions);
|
||||
}
|
||||
|
||||
async rename(_from: URI, _to: URI, _opts: IFileOverwriteOptions): Promise<void> {
|
||||
throw createFileSystemProviderError('Operation not supported', FileSystemProviderErrorCode.NoPermissions);
|
||||
}
|
||||
|
||||
// --- Cache management
|
||||
|
||||
invalidateCache(owner: string, repo: string, ref: string): void {
|
||||
this.treeCache.delete(this.getCacheKey(owner, repo, ref));
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.treeCache.clear();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.file-tree-view-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-tree-view-body .file-tree-welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
gap: 8px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.file-tree-view-body .file-tree-welcome-icon {
|
||||
font-size: 24px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.file-tree-view-body .file-tree-welcome-message {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-tree-view-body .file-tree-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -124,10 +124,11 @@
|
||||
.ai-customization-toolbar .ai-customization-toolbar-content {
|
||||
max-height: 500px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.2s ease-out;
|
||||
transition: max-height 0.2s ease-out, display 0s linear 0.2s;
|
||||
}
|
||||
|
||||
.ai-customization-toolbar.collapsed .ai-customization-toolbar-content {
|
||||
max-height: 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<boolean>('isNewChatSession', true);
|
||||
|
||||
@@ -81,10 +81,16 @@ export interface ISessionsManagementService {
|
||||
createNewPendingSession(pendingSessionResource: URI): Promise<IActiveSessionItem>;
|
||||
|
||||
/**
|
||||
* Open a new session, apply options, and send the initial request.
|
||||
* This is the main entry point for the new-chat welcome widget.
|
||||
* Create a pending session object for the given target type.
|
||||
* Local sessions collect options locally; remote sessions notify the extension.
|
||||
*/
|
||||
sendRequestForNewSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap<string, IChatSessionProviderOptionItem>, folderUri?: URI): Promise<void>;
|
||||
createNewSessionForTarget(target: AgentSessionProviders, sessionResource: URI, defaultRepoUri?: URI): Promise<INewSession>;
|
||||
|
||||
/**
|
||||
* Open a new session, apply options, and send the initial request.
|
||||
* Looks up the session by resource URI and builds send options from it.
|
||||
*/
|
||||
sendRequestForNewSession(sessionResource: URI): Promise<void>;
|
||||
|
||||
/**
|
||||
* 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<IActiveSessionItem | undefined>(this, undefined);
|
||||
readonly activeSession: IObservable<IActiveSessionItem | undefined> = this._activeSession;
|
||||
|
||||
private readonly _newSessions = new Map<string, INewSession>();
|
||||
private lastSelectedSession: URI | undefined;
|
||||
private readonly isNewChatSessionContext: IContextKey<boolean>;
|
||||
|
||||
@@ -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<INewSession> {
|
||||
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<string, IChatSessionProviderOptionItem>, folderUri?: URI): Promise<void> {
|
||||
if (LocalChatSessionUri.isLocalSession(sessionResource)) {
|
||||
await this.sendLocalSession(sessionResource, query, sendOptions, folderUri);
|
||||
} else {
|
||||
await this.sendCustomSession(sessionResource, query, sendOptions, selectedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<void> {
|
||||
if (folderUri) {
|
||||
await this.workspaceEditingService.updateFolders(0, this.workspaceContextService.getWorkspace().folders.length, [{ uri: folderUri }]);
|
||||
async sendRequestForNewSession(sessionResource: URI): Promise<void> {
|
||||
const session = this._newSessions.get(sessionResource.toString());
|
||||
if (!session) {
|
||||
this.logService.error(`[SessionsManagementService] No new session found for resource: ${sessionResource.toString()}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.openSession(sessionResource);
|
||||
|
||||
const widget = this.chatWidgetService.lastFocusedWidget;
|
||||
if (widget) {
|
||||
if (sendOptions.attachedContext?.length) {
|
||||
widget.attachmentModel.addContext(...sendOptions.attachedContext);
|
||||
}
|
||||
widget.setInput(query);
|
||||
widget.acceptInput(query);
|
||||
const query = session.query;
|
||||
if (!query) {
|
||||
this.logService.error('[SessionsManagementService] No query set on session');
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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<string, IChatSessionProviderOptionItem>): Promise<void> {
|
||||
// 1. Open the session - loads the model and shows the ChatViewPane
|
||||
|
||||
@@ -9,6 +9,7 @@ import './media/sessionsViewPane.css';
|
||||
import * as DOM from '../../../../base/browser/dom.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
import { MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { autorun } from '../../../../base/common/observable.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
@@ -221,6 +222,7 @@ export class AgenticSessionsViewPane extends ViewPane {
|
||||
updateHeaderTotalCount();
|
||||
|
||||
// Toggle collapse on header click
|
||||
const transitionListener = this._register(new MutableDisposable());
|
||||
const toggleCollapse = () => {
|
||||
const collapsed = container.classList.toggle('collapsed');
|
||||
header.classList.toggle('collapsed', collapsed);
|
||||
@@ -230,14 +232,13 @@ export class AgenticSessionsViewPane extends ViewPane {
|
||||
chevron.classList.add(...ThemeIcon.asClassNameArray(collapsed ? Codicon.chevronRight : Codicon.chevronDown));
|
||||
|
||||
// Re-layout after the transition so sessions control gets the right height
|
||||
const onTransitionEnd = () => {
|
||||
toolbarContainer.removeEventListener('transitionend', onTransitionEnd);
|
||||
transitionListener.value = DOM.addDisposableListener(toolbarContainer, 'transitionend', () => {
|
||||
transitionListener.clear();
|
||||
if (this.viewPaneContainer) {
|
||||
const { offsetHeight, offsetWidth } = this.viewPaneContainer;
|
||||
this.layoutBody(offsetHeight, offsetWidth);
|
||||
}
|
||||
};
|
||||
toolbarContainer.addEventListener('transitionend', onTransitionEnd);
|
||||
});
|
||||
};
|
||||
|
||||
this._register(headerButton.onDidClick(() => toggleCollapse()));
|
||||
|
||||
@@ -106,7 +106,6 @@ class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements IAuxilia
|
||||
|
||||
constructor(
|
||||
readonly container: HTMLElement,
|
||||
editorGroupsContainer: IEditorGroupsContainer,
|
||||
private readonly mainTitlebar: TitlebarPart,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@@ -133,7 +132,7 @@ export class NativeTitleService extends TitleService {
|
||||
return this.instantiationService.createInstance(MainNativeTitlebarPart);
|
||||
}
|
||||
|
||||
protected override doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): AuxiliaryNativeTitlebarPart {
|
||||
return instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, editorGroupsContainer, this.mainPart);
|
||||
protected override doCreateAuxiliaryTitlebarPart(container: HTMLElement, _editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): AuxiliaryNativeTitlebarPart {
|
||||
return instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, this.mainPart);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user