Merge branch 'main' into tyriar/287652_2

This commit is contained in:
Paul
2026-01-29 15:07:52 -08:00
committed by GitHub
223 changed files with 5267 additions and 1943 deletions
+1
View File
@@ -139,6 +139,7 @@ function f(x: number, y: string): void { }
- When adding tooltips to UI elements, prefer the use of IHoverService service.
- 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.
## 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.
+1 -1
View File
@@ -12,7 +12,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "$NOT_TEAM_MEMBERS=-author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:ntrogh -author:hediet -author:isidorn -author:joaomoreno -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:ulugbekna -author:aiday-mar -author:bhavyaus -author:justschen -author:benibenj -author:luabud -author:anthonykim1 -author:joshspicer -author:osortega -author:hawkticehurst -author:pierceboggan -author:benvillalobos -author:dileepyavan -author:dineshc-msft -author:dmitrivMS -author:eli-w-king -author:jo-oikawa -author:jruales -author:jytjyt05 -author:kycutler -author:mrleemurray -author:pwang347 -author:vijayupadya -author:bryanchen-d -author:cwebster-99"
"value": "$NOT_TEAM_MEMBERS=-author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:ntrogh -author:hediet -author:isidorn -author:joaomoreno -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:ulugbekna -author:aiday-mar -author:bhavyaus -author:justschen -author:benibenj -author:luabud -author:anthonykim1 -author:joshspicer -author:osortega -author:hawkticehurst -author:pierceboggan -author:benvillalobos -author:dileepyavan -author:dineshc-msft -author:dmitrivMS -author:eli-w-king -author:jo-oikawa -author:jruales -author:jytjyt05 -author:kycutler -author:mrleemurray -author:pwang347 -author:vijayupadya -author:bryanchen-d -author:cwebster-99 -author:rwoll -author:lostintangent -author:jukasper -author:zhichli"
},
{
"kind": 1,
@@ -89,9 +89,9 @@ jobs:
- ${{ if ne(parameters.container, '') }}:
- task: Cache@2
inputs:
key: 'docker-v3 | "${{ parameters.container }}" | "${{ parameters.arch }}" | "$(Agent.OS)" | $(TEST_DIR)/containers/${{ parameters.container }}.dockerfile'
key: 'docker-v3 | "${{ parameters.container }}" | "${{ parameters.arch }}" | "${{ parameters.pageSize }}" | "$(Agent.OS)" | $(TEST_DIR)/containers/${{ parameters.container }}.dockerfile'
path: $(DOCKER_CACHE_DIR)
restoreKeys: docker-v3 | "${{ parameters.container }}" | "${{ parameters.arch }}" | "$(Agent.OS)"
restoreKeys: docker-v3 | "${{ parameters.container }}" | "${{ parameters.arch }}" | "${{ parameters.pageSize }}" | "$(Agent.OS)"
cacheHitVar: DOCKER_CACHE_HIT
displayName: Download Docker Image
@@ -110,16 +110,16 @@ jobs:
--quality "$(BUILD_QUALITY)" \
--commit "$(BUILD_COMMIT)" \
--test-results "/root/results.xml" \
--verbose
--verbose \
${{ parameters.args }}
workingDirectory: $(TEST_DIR)
displayName: Run Sanity Tests
- ${{ if eq(parameters.pageSize, '') }}:
- bash: |
mkdir -p "$(DOCKER_CACHE_DIR)"
docker save -o "$(DOCKER_CACHE_FILE)" "${{ parameters.container }}"
condition: and(succeeded(), ne(variables.DOCKER_CACHE_HIT, 'true'))
displayName: Save Docker Image
- bash: |
mkdir -p "$(DOCKER_CACHE_DIR)"
docker save -o "$(DOCKER_CACHE_FILE)" "${{ parameters.container }}"
condition: and(succeeded(), ne(variables.DOCKER_CACHE_HIT, 'true'))
displayName: Save Docker Image
- task: PublishTestResults@2
inputs:
@@ -7,7 +7,7 @@ parameters:
jobs:
- job: macOS${{ parameters.VSCODE_TEST_SUITE }}
displayName: ${{ parameters.VSCODE_TEST_SUITE }} Tests
timeoutInMinutes: 30
timeoutInMinutes: 90
variables:
VSCODE_ARCH: arm64
templateContext:
@@ -17,7 +17,6 @@ jobs:
name: 1es-ubuntu-22.04-x64
os: linux
timeoutInMinutes: 40000
continueOnError: true
variables:
VSCODE_ARCH: x64
steps:
@@ -106,6 +105,12 @@ jobs:
timeoutInMinutes: 400
condition: and(succeeded(), eq(variables['SHOULD_VALIDATE'], 'true'))
- script: |
set -e
find . -name 'package-lock.json' -exec sed -i "s|$NPM_REGISTRY|https://registry.npmjs.org/|g" {} \;
displayName: Restore registry URLs in package-lock.json
condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none'), eq(variables['SHOULD_VALIDATE'], 'true'))
- script: .github/workflows/check-clean-git-state.sh
displayName: Check clean git state
condition: and(succeeded(), eq(variables['SHOULD_VALIDATE'], 'true'))
+18 -17
View File
@@ -94,11 +94,11 @@ extends:
os: windows
args: --no-detection --grep "win32-arm64"
# Alpine 3.23
# Alpine 3.22
- template: build/azure-pipelines/common/sanity-tests.yml@self
parameters:
name: alpine_amd64
displayName: Alpine 3.23 amd64
displayName: Alpine 3.22 amd64
poolName: 1es-ubuntu-22.04-x64
container: alpine
arch: amd64
@@ -106,8 +106,8 @@ extends:
- template: build/azure-pipelines/common/sanity-tests.yml@self
parameters:
name: alpine_arm64
displayName: Alpine 3.23 arm64
poolName: 1es-mariner-2.0-arm64
displayName: Alpine 3.22 arm64
poolName: 1es-azure-linux-3-arm64
container: alpine
arch: arm64
@@ -124,7 +124,7 @@ extends:
parameters:
name: centos_stream9_arm64
displayName: CentOS Stream 9 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: centos
arch: arm64
@@ -141,7 +141,7 @@ extends:
parameters:
name: debian_10_arm32
displayName: Debian 10 arm32
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: debian-10
arch: arm
@@ -149,7 +149,7 @@ extends:
parameters:
name: debian_10_arm64
displayName: Debian 10 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: debian-10
arch: arm64
@@ -166,7 +166,7 @@ extends:
parameters:
name: debian_12_arm32
displayName: Debian 12 arm32
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: debian-12
arch: arm
@@ -174,7 +174,7 @@ extends:
parameters:
name: debian_12_arm64
displayName: Debian 12 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: debian-12
arch: arm64
@@ -192,7 +192,7 @@ extends:
parameters:
name: fedora_36_arm64
displayName: Fedora 36 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: fedora
baseImage: fedora:36
arch: arm64
@@ -211,7 +211,7 @@ extends:
parameters:
name: fedora_40_arm64
displayName: Fedora 40 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: fedora
baseImage: fedora:40
arch: arm64
@@ -229,7 +229,7 @@ extends:
parameters:
name: opensuse_leap_arm64
displayName: openSUSE Leap 16.0 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: opensuse
arch: arm64
@@ -246,7 +246,7 @@ extends:
parameters:
name: redhat_ubi9_arm64
displayName: Red Hat UBI 9 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: redhat
arch: arm64
@@ -272,7 +272,7 @@ extends:
parameters:
name: ubuntu_22_04_arm32
displayName: Ubuntu 22.04 arm32
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: ubuntu
baseImage: ubuntu:22.04
arch: arm
@@ -281,7 +281,7 @@ extends:
parameters:
name: ubuntu_22_04_arm64
displayName: Ubuntu 22.04 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: ubuntu
baseImage: ubuntu:22.04
arch: arm64
@@ -300,7 +300,7 @@ extends:
parameters:
name: ubuntu_24_04_arm32
displayName: Ubuntu 24.04 arm32
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: ubuntu
baseImage: ubuntu:24.04
arch: arm
@@ -309,7 +309,7 @@ extends:
parameters:
name: ubuntu_24_04_arm64
displayName: Ubuntu 24.04 arm64
poolName: 1es-mariner-2.0-arm64
poolName: 1es-azure-linux-3-arm64
container: ubuntu
baseImage: ubuntu:24.04
arch: arm64
@@ -323,3 +323,4 @@ extends:
baseImage: ubuntu:24.04
arch: arm64
pageSize: 64k
args: --grep "desktop-linux-arm64"
+1 -1
View File
@@ -106,7 +106,7 @@ async function main(buildDir?: string, outDir?: string): Promise<void> {
'text-size': 12,
window: {
position: { x: 100, y: 400 },
size: { width: 480, height: 320 }
size: { width: 480, height: 352 }
},
contents: [
{
+18 -5
View File
@@ -98,14 +98,22 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string,
const result = es.through();
const packagedDependencies: string[] = [];
const stripOutSourceMaps: string[] = [];
const packageJsonConfig = require(path.join(extensionPath, 'package.json'));
if (packageJsonConfig.dependencies) {
const webpackRootConfig = require(path.join(extensionPath, webpackConfigFileName)).default;
const webpackConfig = require(path.join(extensionPath, webpackConfigFileName));
const webpackRootConfig = webpackConfig.default;
for (const key in webpackRootConfig.externals) {
if (key in packageJsonConfig.dependencies) {
packagedDependencies.push(key);
}
}
if (webpackConfig.StripOutSourceMaps) {
for (const filePath of webpackConfig.StripOutSourceMaps) {
stripOutSourceMaps.push(filePath);
}
}
}
// TODO: add prune support based on packagedDependencies to vsce.PackageManager.Npm similar
@@ -177,10 +185,15 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string,
// * rewrite sourceMappingURL
// * save to disk so that upload-task picks this up
if (path.extname(data.basename) === '.js') {
const contents = (data.contents as Buffer).toString('utf8');
data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) {
return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`;
}), 'utf8');
if (stripOutSourceMaps.indexOf(data.relative) >= 0) { // remove source map
const contents = (data.contents as Buffer).toString('utf8');
data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, ''), 'utf8');
} else {
const contents = (data.contents as Buffer).toString('utf8');
data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) {
return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`;
}), 'utf8');
}
}
this.emit('data', data);
+4 -1
View File
@@ -50,7 +50,7 @@ export async function fetchUrl(url: string, options: IFetchOptions, retries = 10
startTime = new Date().getTime();
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30 * 1000);
let timeout = setTimeout(() => controller.abort(), 30 * 1000);
try {
const response = await fetch(url, {
...options.nodeFetchOptions,
@@ -60,6 +60,9 @@ export async function fetchUrl(url: string, options: IFetchOptions, retries = 10
log(`Fetch completed: Status ${response.status}. Took ${ansiColors.magenta(`${new Date().getTime() - startTime} ms`)}`);
}
if (response.ok && (response.status >= 200 && response.status < 300)) {
// Reset timeout for body download - large files need more time
clearTimeout(timeout);
timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000);
const contents = Buffer.from(await response.arrayBuffer());
if (options.checksumSha256) {
const actualSHA256Checksum = crypto.createHash('sha256').update(contents).digest('hex');
@@ -39,6 +39,7 @@
"--vscode-button-foreground",
"--vscode-button-hoverBackground",
"--vscode-button-secondaryBackground",
"--vscode-button-secondaryBorder",
"--vscode-button-secondaryForeground",
"--vscode-button-secondaryHoverBackground",
"--vscode-button-separator",
@@ -360,6 +361,7 @@
"--vscode-extensionBadge-remoteBackground",
"--vscode-extensionBadge-remoteForeground",
"--vscode-extensionButton-background",
"--vscode-extensionButton-border",
"--vscode-extensionButton-foreground",
"--vscode-extensionButton-hoverBackground",
"--vscode-extensionButton-prominentBackground",
@@ -384,8 +386,8 @@
"--vscode-inlineChat-background",
"--vscode-inlineChat-border",
"--vscode-inlineChat-foreground",
"--vscode-inlineChat-regionHighlight",
"--vscode-inlineChat-shadow",
"--vscode-inlineChat-regionHighlight",
"--vscode-inlineChatDiff-inserted",
"--vscode-inlineChatDiff-removed",
"--vscode-inlineChatInput-background",
+3 -3
View File
@@ -1069,9 +1069,9 @@
}
},
"node_modules/tar": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
"integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
+35 -1
View File
@@ -11,6 +11,7 @@ import { dirs } from './dirs.ts';
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const root = path.dirname(path.dirname(import.meta.dirname));
const rootNpmrcConfigKeys = getNpmrcConfigKeys(path.join(root, '.npmrc'));
function log(dir: string, message: string) {
if (process.stdout.isTTY) {
@@ -125,6 +126,36 @@ function removeParcelWatcherPrebuild(dir: string) {
}
}
function getNpmrcConfigKeys(npmrcPath: string): string[] {
if (!fs.existsSync(npmrcPath)) {
return [];
}
const lines = fs.readFileSync(npmrcPath, 'utf8').split('\n');
const keys: string[] = [];
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine && !trimmedLine.startsWith('#')) {
const eqIndex = trimmedLine.indexOf('=');
if (eqIndex > 0) {
keys.push(trimmedLine.substring(0, eqIndex).trim());
}
}
}
return keys;
}
function clearInheritedNpmrcConfig(dir: string, env: NodeJS.ProcessEnv): void {
const dirNpmrcPath = path.join(root, dir, '.npmrc');
if (fs.existsSync(dirNpmrcPath)) {
return;
}
for (const key of rootNpmrcConfigKeys) {
const envKey = `npm_config_${key.replace(/-/g, '_')}`;
delete env[envKey];
}
}
for (const dir of dirs) {
if (dir === '') {
@@ -179,7 +210,10 @@ for (const dir of dirs) {
continue;
}
npmInstall(dir, opts);
// For directories that don't define their own .npmrc, clear inherited config
const env = { ...process.env };
clearInheritedNpmrcConfig(dir, env);
npmInstall(dir, { env });
}
child_process.execSync('git config pull.rebase merges');
+125 -240
View File
@@ -53,7 +53,7 @@
"ansi-colors": "^3.2.3",
"byline": "^5.0.0",
"debug": "^4.3.2",
"dmg-builder": "^26.5.0",
"dmg-builder": "^26.6.0",
"esbuild": "0.27.2",
"extract-zip": "^2.0.1",
"gulp-merge-json": "^2.1.1",
@@ -813,14 +813,13 @@
}
},
"node_modules/@electron/rebuild": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.1.tgz",
"integrity": "sha512-iMGXb6Ib7H/Q3v+BKZJoETgF9g6KMNZVbsO4b7Dmpgb5qTFqyFTzqW9F3TOSHdybv2vKYKzSS9OiZL+dcJb+1Q==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz",
"integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@malept/cross-spawn-promise": "^2.0.0",
"chalk": "^4.0.0",
"debug": "^4.1.1",
"detect-libc": "^2.0.1",
"got": "^11.7.0",
@@ -831,7 +830,7 @@
"ora": "^5.1.0",
"read-binary-file-arch": "^1.0.6",
"semver": "^7.3.5",
"tar": "^6.0.5",
"tar": "^7.5.6",
"yargs": "^17.0.1"
},
"bin": {
@@ -841,142 +840,6 @@
"node": ">=22.12.0"
}
},
"node_modules/@electron/rebuild/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@electron/rebuild/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@electron/rebuild/node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/@electron/rebuild/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@electron/rebuild/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/@electron/rebuild/node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@electron/rebuild/node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@electron/rebuild/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@electron/rebuild/node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=8"
}
},
"node_modules/@electron/rebuild/node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@electron/rebuild/node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@electron/rebuild/node_modules/node-abi": {
"version": "4.26.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.26.0.tgz",
@@ -1003,38 +866,6 @@
"node": ">=10"
}
},
"node_modules/@electron/rebuild/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@electron/rebuild/node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me",
"dev": true,
"license": "ISC",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@electron/universal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz",
@@ -3136,18 +2967,19 @@
"license": "MIT"
},
"node_modules/app-builder-lib": {
"version": "26.5.0",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.5.0.tgz",
"integrity": "sha512-iRRiJhM0uFMauDeIuv8ESHZSn+LESbdDEuHi7rKdeETjrvBObecXnWJx1f3vs3KtoGcd3hCk1zURKypyvZOtFQ==",
"version": "26.6.0",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.6.0.tgz",
"integrity": "sha512-P2naoSaGOqJY54cqTceO9lms2M790UM7BA8AlOuaolQhRp/LOshAVc4vzVlYFw4YNPtiuBJqdAhWALuoEKnayQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@develar/schema-utils": "~2.6.5",
"@electron/asar": "3.4.1",
"@electron/fuses": "^1.8.0",
"@electron/get": "^3.0.0",
"@electron/notarize": "2.5.0",
"@electron/osx-sign": "1.3.3",
"@electron/rebuild": "4.0.1",
"@electron/rebuild": "^4.0.3",
"@electron/universal": "2.0.3",
"@malept/flatpak-bundler": "^0.4.0",
"@types/fs-extra": "9.0.13",
@@ -3160,7 +2992,7 @@
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"ejs": "^3.1.8",
"electron-publish": "26.4.1",
"electron-publish": "26.6.0",
"fs-extra": "^10.1.0",
"hosted-git-info": "^4.1.0",
"isbinaryfile": "^5.0.0",
@@ -3170,9 +3002,10 @@
"lazy-val": "^1.0.5",
"minimatch": "^10.0.3",
"plist": "3.1.0",
"proper-lockfile": "^4.1.2",
"resedit": "^1.7.0",
"semver": "~7.7.3",
"tar": "7.5.3",
"tar": "^7.5.6",
"temp-file": "^3.4.0",
"tiny-async-pool": "1.3.0",
"which": "^5.0.0"
@@ -3181,8 +3014,55 @@
"node": ">=14.0.0"
},
"peerDependencies": {
"dmg-builder": "26.5.0",
"electron-builder-squirrel-windows": "26.5.0"
"dmg-builder": "26.6.0",
"electron-builder-squirrel-windows": "26.6.0"
}
},
"node_modules/app-builder-lib/node_modules/@electron/get": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz",
"integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"env-paths": "^2.2.0",
"fs-extra": "^8.1.0",
"got": "^11.8.5",
"progress": "^2.0.3",
"semver": "^6.2.0",
"sumchecker": "^3.0.1"
},
"engines": {
"node": ">=14"
},
"optionalDependencies": {
"global-agent": "^3.0.0"
}
},
"node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/app-builder-lib/node_modules/@electron/osx-sign": {
@@ -3242,6 +3122,29 @@
"node": ">=12"
}
},
"node_modules/app-builder-lib/node_modules/fs-extra/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/app-builder-lib/node_modules/fs-extra/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/app-builder-lib/node_modules/isexe": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
@@ -3265,19 +3168,6 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/app-builder-lib/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/app-builder-lib/node_modules/minimatch": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
@@ -3307,16 +3197,6 @@
"node": ">=10"
}
},
"node_modules/app-builder-lib/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/app-builder-lib/node_modules/which": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",
@@ -3398,6 +3278,13 @@
"node": ">=8"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"dev": true,
"license": "MIT"
},
"node_modules/async-done": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz",
@@ -4595,13 +4482,13 @@
}
},
"node_modules/dmg-builder": {
"version": "26.5.0",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.5.0.tgz",
"integrity": "sha512-AyOCzpS1TCxDkSWxAzpfw5l7jBX4C8jKCucmT/6y6/24H5VKSHpjcVJD0W8o5BrFi+skC7Z7+F4aNyHmvn4AAw==",
"version": "26.6.0",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.6.0.tgz",
"integrity": "sha512-IkGlOLfJ3q7y9iaDMnNSArDdPg3Ntx8Ps6aL7yTEIpL6znA+t5L/LRTAGFz1J/12hM/NiNEYg0LoBEheqGdZXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"app-builder-lib": "26.5.0",
"app-builder-lib": "26.6.0",
"builder-util": "26.4.1",
"fs-extra": "^10.1.0",
"iconv-lite": "^0.6.2",
@@ -4883,9 +4770,9 @@
}
},
"node_modules/electron-publish": {
"version": "26.4.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.4.1.tgz",
"integrity": "sha512-nByal9K5Ar3BNJUfCSglXltpKUhJqpwivNpKVHnkwxTET9LKl+NxoojpGF1dSXVFcoBKVm+OhsVa28ZsoshEPA==",
"version": "26.6.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.6.0.tgz",
"integrity": "sha512-LsyHMMqbvJ2vsOvuWJ19OezgF2ANdCiHpIucDHNiLhuI+/F3eW98ouzWSRmXXi82ZOPZXC07jnIravY4YYwCLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4893,7 +4780,7 @@
"builder-util": "26.4.1",
"builder-util-runtime": "9.5.1",
"chalk": "^4.1.2",
"form-data": "^4.0.0",
"form-data": "^4.0.5",
"fs-extra": "^10.1.0",
"lazy-val": "^1.0.5",
"mime": "^2.5.2"
@@ -5513,9 +5400,9 @@
"dev": true
},
"node_modules/form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6332,13 +6219,6 @@
"node": ">=10"
}
},
"node_modules/jake/node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"dev": true,
"license": "MIT"
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
@@ -7011,19 +6891,6 @@
"node": ">= 18"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@@ -7907,6 +7774,25 @@
"node": ">=10"
}
},
"node_modules/proper-lockfile": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"retry": "^0.12.0",
"signal-exit": "^3.0.2"
}
},
"node_modules/proper-lockfile/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true,
"license": "ISC"
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -9054,10 +8940,9 @@
}
},
"node_modules/tar": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz",
"integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==",
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me",
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
+1 -1
View File
@@ -47,7 +47,7 @@
"ansi-colors": "^3.2.3",
"byline": "^5.0.0",
"debug": "^4.3.2",
"dmg-builder": "^26.5.0",
"dmg-builder": "^26.6.0",
"esbuild": "0.27.2",
"extract-zip": "^2.0.1",
"gulp-merge-json": "^2.1.1",
+2 -2
View File
@@ -1564,7 +1564,7 @@ begin
#if "user" == InstallTarget
ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('Add-AppxPackage -Path ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx\{#AppxPackage}') + ''' -ExternalLocation ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx') + ''''), '', SW_HIDE, ewWaitUntilTerminated, AddAppxPackageResultCode);
#else
ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('Add-AppxPackage -Stage ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx\{#AppxPackage}') + ''' -ExternalLocation ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx') + '''; Add-AppxProvisionedPackage -Online -SkipLicense -PackagePath ''' + ExpandConstant('{app}\appx\{#AppxPackage}') + ''''), '', SW_HIDE, ewWaitUntilTerminated, AddAppxPackageResultCode);
ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('Add-AppxPackage -Stage ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx\{#AppxPackage}') + ''' -ExternalLocation ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx') + '''; Add-AppxProvisionedPackage -Online -SkipLicense -PackagePath ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx\{#AppxPackage}') + ''''), '', SW_HIDE, ewWaitUntilTerminated, AddAppxPackageResultCode);
#endif
Log('Add-AppxPackage complete.');
end;
@@ -1589,7 +1589,7 @@ begin
#if "user" == InstallTarget
ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('Remove-AppxPackage -Package ''' + AppxPackageFullname + ''''), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode);
#else
ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('$packages = Get-AppxPackage ''' + AppxPackageFullname + '''; foreach ($package in $packages) { Remove-AppxProvisionedPackage -PackageName $package.PackageFullName -Online }; foreach ($package in $packages) { Remove-AppxPackage -Package $package.PackageFullName -AllUsers }'), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode);
ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('$packages = Get-AppxPackage ''' + ExpandConstant('{#AppxPackageName}') + '''; foreach ($package in $packages) { Remove-AppxProvisionedPackage -PackageName $package.PackageFullName -Online }; foreach ($package in $packages) { Remove-AppxPackage -Package $package.PackageFullName -AllUsers }'), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode);
#endif
Log('Remove-AppxPackage for current appx installation complete.');
end;
@@ -13,3 +13,5 @@ export default withDefaults({
['git-editor-main']: './src/git-editor-main.ts'
}
});
export const StripOutSourceMaps = ['dist/askpass-main.js'];
+5 -2
View File
@@ -100,8 +100,11 @@ export class ApiRepository implements Repository {
filterEvent(this.#repository.onDidRunOperation, e => e.operation.kind === OperationKind.Checkout || e.operation.kind === OperationKind.CheckoutTracking), () => null);
}
apply(patch: string, reverse?: boolean): Promise<void> {
return this.#repository.apply(patch, reverse);
apply(patch: string, reverse?: boolean): Promise<void>;
apply(patch: string, options?: { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean }): Promise<void>;
apply(patch: string, reverseOrOptions?: boolean | { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean }): Promise<void> {
const options = typeof reverseOrOptions === 'boolean' ? { reverse: reverseOrOptions } : reverseOrOptions;
return this.#repository.apply(patch, options);
}
getConfigs(): Promise<{ key: string; value: string }[]> {
+1
View File
@@ -256,6 +256,7 @@ export interface Repository {
clean(paths: string[]): Promise<void>;
apply(patch: string, reverse?: boolean): Promise<void>;
apply(patch: string, options?: { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean; }): Promise<void>;
diff(cached?: boolean): Promise<string>;
diffWithHEAD(): Promise<Change[]>;
diffWithHEAD(path: string): Promise<string>;
+81 -2
View File
@@ -128,6 +128,74 @@ async function copyFileSecure(
await setWindowsPermissions(dest, logger);
}
/**
* Updates the modification time of a directory to mark it as recently used.
*/
async function updateDirectoryMtime(dirPath: string, logger: LogOutputChannel): Promise<void> {
try {
const now = new Date();
await fs.promises.utimes(dirPath, now, now);
logger.trace(`[askpassManager] Updated mtime for ${dirPath}`);
} catch (err) {
logger.warn(`[askpassManager] Failed to update mtime for ${dirPath}: ${err}`);
}
}
/**
* Garbage collects old content-addressed askpass directories that haven't been used in 7 days.
* This prevents accumulation of old versions when VS Code updates.
*/
async function garbageCollectOldDirectories(
askpassBaseDir: string,
currentHash: string,
logger: LogOutputChannel
): Promise<void> {
try {
// Check if the askpass base directory exists
try {
await fs.promises.access(askpassBaseDir);
} catch {
// Directory doesn't exist, nothing to clean
return;
}
const entries = await fs.promises.readdir(askpassBaseDir);
const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
for (const entry of entries) {
// Skip the current content-addressed directory
if (entry === currentHash) {
continue;
}
const entryPath = path.join(askpassBaseDir, entry);
try {
const stat = await fs.promises.stat(entryPath);
// Only process directories
if (!stat.isDirectory()) {
continue;
}
// Check if the directory hasn't been used in 7 days
if (stat.mtime.getTime() < sevenDaysAgo) {
logger.info(`[askpassManager] Removing old askpass directory: ${entryPath} (last used: ${stat.mtime.toISOString()})`);
// Remove the directory and all its contents
await fs.promises.rm(entryPath, { recursive: true, force: true });
} else {
logger.trace(`[askpassManager] Keeping askpass directory: ${entryPath} (last used: ${stat.mtime.toISOString()})`);
}
} catch (err) {
logger.warn(`[askpassManager] Failed to process/remove directory ${entryPath}: ${err}`);
}
}
} catch (err) {
logger.warn(`[askpassManager] Failed to garbage collect old directories: ${err}`);
}
}
export interface AskpassPaths {
readonly askpass: string;
readonly askpassMain: string;
@@ -144,7 +212,7 @@ export interface AskpassPaths {
* @param storageDir The user-controlled storage directory (context.storageUri.fsPath)
* @param logger Logger for diagnostic output
*/
async function ensureAskpassScripts(
export async function ensureAskpassScripts(
sourceDir: string,
storageDir: string,
logger: LogOutputChannel
@@ -162,7 +230,8 @@ async function ensureAskpassScripts(
logger.trace(`[askpassManager] Content hash: ${contentHash}`);
// Create content-addressed directory
const askpassDir = path.join(storageDir, 'askpass', contentHash);
const askpassBaseDir = path.join(storageDir, 'askpass');
const askpassDir = path.join(askpassBaseDir, contentHash);
const destPaths: AskpassPaths = {
askpass: path.join(askpassDir, 'askpass.sh'),
@@ -177,6 +246,10 @@ async function ensureAskpassScripts(
const stat = await fs.promises.stat(destPaths.askpass);
if (stat.isFile()) {
logger.trace(`[askpassManager] Using existing content-addressed askpass at ${askpassDir}`);
// Update mtime to mark this directory as recently used
await updateDirectoryMtime(askpassDir, logger);
return destPaths;
}
} catch {
@@ -200,6 +273,12 @@ async function ensureAskpassScripts(
logger.info(`[askpassManager] Successfully created content-addressed askpass scripts`);
// Update mtime to mark this directory as recently used
await updateDirectoryMtime(askpassDir, logger);
// Garbage collect old directories
await garbageCollectOldDirectories(askpassBaseDir, contentHash, logger);
return destPaths;
}
+11 -3
View File
@@ -1679,11 +1679,19 @@ export class Repository {
}
}
async apply(patch: string, reverse?: boolean): Promise<void> {
async apply(patch: string, options?: { reverse?: boolean; threeWay?: boolean; allowEmpty?: boolean }): Promise<void> {
const args = ['apply', patch];
if (reverse) {
args.push('-R');
if (options?.allowEmpty) {
args.push('--allow-empty');
}
if (options?.reverse) {
args.push('--reverse');
}
if (options?.threeWay) {
args.push('--3way');
}
try {
+22
View File
@@ -290,6 +290,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
this._unsafeRepositoriesManager = new UnsafeRepositoriesManager();
workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);
workspace.onDidChangeWorkspaceTrustedFolders(this.onDidChangeWorkspaceTrustedFolders, this, this.disposables);
window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);
window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, this.disposables);
workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);
@@ -488,6 +489,27 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
}
}
private async onDidChangeWorkspaceTrustedFolders(): Promise<void> {
try {
const openRepositoriesToDispose: OpenRepository[] = [];
for (const openRepository of this.openRepositories) {
const dotGitPath = openRepository.repository.dotGit.commonPath ?? openRepository.repository.dotGit.path;
const isTrusted = await workspace.isResourceTrusted(Uri.file(path.dirname(dotGitPath)));
if (!isTrusted) {
openRepositoriesToDispose.push(openRepository);
this.logger.trace(`[Model][onDidChangeWorkspaceTrustedFolders] Repository is no longer trusted: ${openRepository.repository.root}`);
}
}
openRepositoriesToDispose.forEach(r => r.dispose());
}
catch (err) {
this.logger.warn(`[Model][onDidChangeWorkspaceTrustedFolders] Error: ${err}`);
}
}
private onDidChangeConfiguration(): void {
const possibleRepositoryFolders = (workspace.workspaceFolders || [])
.filter(folder => workspace.getConfiguration('git', folder.uri).get<boolean>('enabled') === true)
+2 -2
View File
@@ -2387,8 +2387,8 @@ export class Repository implements Disposable {
return this.run(Operation.Show, () => this.repository.detectObjectType(object));
}
async apply(patch: string, reverse?: boolean): Promise<void> {
return await this.run(Operation.Apply, () => this.repository.apply(patch, reverse));
async apply(patch: string, options?: { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean }): Promise<void> {
return await this.run(Operation.Apply, () => this.repository.apply(patch, options));
}
async getStashes(): Promise<Stash[]> {
@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'mocha';
import * as assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { ensureAskpassScripts } from '../askpassManager';
import { Event, EventEmitter, LogLevel, LogOutputChannel } from 'vscode';
class MockLogOutputChannel implements LogOutputChannel {
logLevel: LogLevel = LogLevel.Info;
onDidChangeLogLevel: Event<LogLevel> = new EventEmitter<LogLevel>().event;
private logs: { level: string; message: string }[] = [];
trace(message: string, ..._args: any[]): void {
this.logs.push({ level: 'trace', message });
}
debug(message: string, ..._args: any[]): void {
this.logs.push({ level: 'debug', message });
}
info(message: string, ..._args: any[]): void {
this.logs.push({ level: 'info', message });
}
warn(message: string, ..._args: any[]): void {
this.logs.push({ level: 'warn', message });
}
error(error: string | Error, ..._args: any[]): void {
this.logs.push({ level: 'error', message: error.toString() });
}
name: string = 'MockLogOutputChannel';
append(_value: string): void { }
appendLine(_value: string): void { }
replace(_value: string): void { }
clear(): void { }
show(_column?: unknown, _preserveFocus?: unknown): void { }
hide(): void { }
dispose(): void { }
getLogs(): { level: string; message: string }[] {
return this.logs;
}
hasLog(level: string, messageSubstring: string): boolean {
return this.logs.some(log => log.level === level && log.message.includes(messageSubstring));
}
}
// Helper to set mtime on a directory
async function setDirectoryMtime(dirPath: string, mtime: Date): Promise<void> {
await fs.promises.utimes(dirPath, mtime, mtime);
}
suite('askpassManager', () => {
let tempDir: string;
let sourceDir: string;
setup(async () => {
// Create a temporary directory for testing
tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'askpass-test-'));
// Create source directory with dummy askpass files
sourceDir = path.join(tempDir, 'source');
await fs.promises.mkdir(sourceDir, { recursive: true });
const askpassFiles = ['askpass.sh', 'askpass-main.js', 'ssh-askpass.sh', 'askpass-empty.sh', 'ssh-askpass-empty.sh'];
for (const file of askpassFiles) {
await fs.promises.writeFile(path.join(sourceDir, file), `#!/bin/sh\n# ${file}\n`);
}
});
teardown(async () => {
// Clean up temporary directory
try {
await fs.promises.rm(tempDir, { recursive: true, force: true });
} catch {
// Ignore errors during cleanup
}
});
test('garbage collection removes old directories', async function () {
const storageDir = path.join(tempDir, 'storage');
const askpassBaseDir = path.join(storageDir, 'askpass');
const logger = new MockLogOutputChannel();
// Create old directories with old mtimes (8 days ago)
const oldDate = new Date(Date.now() - (8 * 24 * 60 * 60 * 1000));
const oldDirs = ['oldhash1', 'oldhash2'];
for (const dirName of oldDirs) {
const dirPath = path.join(askpassBaseDir, dirName);
await fs.promises.mkdir(dirPath, { recursive: true });
await fs.promises.writeFile(path.join(dirPath, 'test.txt'), 'old');
await setDirectoryMtime(dirPath, oldDate);
}
// Create a recent directory (1 day ago)
const recentDate = new Date(Date.now() - (1 * 24 * 60 * 60 * 1000));
const recentDir = path.join(askpassBaseDir, 'recenthash');
await fs.promises.mkdir(recentDir, { recursive: true });
await fs.promises.writeFile(path.join(recentDir, 'test.txt'), 'recent');
await setDirectoryMtime(recentDir, recentDate);
// Call ensureAskpassScripts which should trigger garbage collection when creating a new directory
await ensureAskpassScripts(sourceDir, storageDir, logger);
// Check that old directories were removed
for (const dirName of oldDirs) {
const dirPath = path.join(askpassBaseDir, dirName);
const exists = await fs.promises.access(dirPath).then(() => true).catch(() => false);
assert.strictEqual(exists, false, `Old directory ${dirName} should have been removed`);
}
// Check that recent directory still exists
const recentExists = await fs.promises.access(recentDir).then(() => true).catch(() => false);
assert.strictEqual(recentExists, true, 'Recent directory should still exist');
// Check logs
assert.ok(logger.hasLog('info', 'Removing old askpass directory'), 'Should log removal of old directories');
});
test('garbage collection skips non-directory entries', async function () {
const storageDir = path.join(tempDir, 'storage');
const askpassBaseDir = path.join(storageDir, 'askpass');
const logger = new MockLogOutputChannel();
// Create a file in the askpass directory (not a directory)
await fs.promises.mkdir(askpassBaseDir, { recursive: true });
const filePath = path.join(askpassBaseDir, 'somefile.txt');
await fs.promises.writeFile(filePath, 'test');
// Set old mtime
const oldDate = new Date(Date.now() - (8 * 24 * 60 * 60 * 1000));
await fs.promises.utimes(filePath, oldDate, oldDate);
// Call ensureAskpassScripts which should trigger garbage collection
await ensureAskpassScripts(sourceDir, storageDir, logger);
// Check that file still exists (should not be removed)
const exists = await fs.promises.access(filePath).then(() => true).catch(() => false);
assert.strictEqual(exists, true, 'Non-directory file should not be removed');
});
test('mtime is updated on existing directory', async function () {
const storageDir = path.join(tempDir, 'storage');
const logger = new MockLogOutputChannel();
// Call ensureAskpassScripts to create the directory
const paths1 = await ensureAskpassScripts(sourceDir, storageDir, logger);
// Get the directory path and its initial mtime
const askpassDir = path.dirname(paths1.askpass);
const stat1 = await fs.promises.stat(askpassDir);
const mtime1 = stat1.mtime.getTime();
// Wait a bit to ensure time difference
await new Promise(resolve => setTimeout(resolve, 100));
// Call again (should update mtime)
await ensureAskpassScripts(sourceDir, storageDir, logger);
// Check that mtime was updated
const stat2 = await fs.promises.stat(askpassDir);
const mtime2 = stat2.mtime.getTime();
assert.ok(mtime2 > mtime1, 'Mtime should be updated on subsequent calls');
});
test('garbage collection handles empty askpass directory', async function () {
const storageDir = path.join(tempDir, 'storage');
const logger = new MockLogOutputChannel();
// Don't create any askpass directories, just call ensureAskpassScripts
await ensureAskpassScripts(sourceDir, storageDir, logger);
// Should complete without errors
assert.ok(true, 'Should handle empty or non-existent askpass directory gracefully');
});
test('current content-addressed directory is not removed', async function () {
const storageDir = path.join(tempDir, 'storage');
const logger = new MockLogOutputChannel();
// Create the current content-addressed directory
const paths = await ensureAskpassScripts(sourceDir, storageDir, logger);
const currentDir = path.dirname(paths.askpass);
// Set its mtime to 8 days ago (would normally be removed)
const oldDate = new Date(Date.now() - (8 * 24 * 60 * 60 * 1000));
await setDirectoryMtime(currentDir, oldDate);
// Call again which should trigger GC
await ensureAskpassScripts(sourceDir, storageDir, logger);
// Current directory should still exist
const exists = await fs.promises.access(currentDir).then(() => true).catch(() => false);
assert.strictEqual(exists, true, 'Current content-addressed directory should not be removed');
});
});
@@ -650,8 +650,6 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP
async function getSchemaAssociations(forceRefresh: boolean): Promise<ISchemaAssociation[]> {
if (!schemaAssociationsCache || forceRefresh) {
schemaAssociationsCache = computeSchemaAssociations();
runtime.logOutputChannel.info(`Computed schema associations: ${(await schemaAssociationsCache).map(a => `${a.uri} -> [${a.fileMatch.join(', ')}]`).join('\n')}`);
}
return schemaAssociationsCache;
}
@@ -35,7 +35,7 @@ export class PanZoomHandler {
this.content = content;
this.content.style.transformOrigin = '0 0';
this.container.style.overflow = 'hidden';
this.container.style.cursor = 'grab';
this.container.style.cursor = 'default';
this.setupEventListeners();
}
@@ -77,11 +77,11 @@ export class PanZoomHandler {
if ((e.key === 'Alt' || e.key === 'Shift') && !this.isPanning) {
e.preventDefault();
if (e.altKey && !e.shiftKey) {
this.container.style.cursor = 'zoom-in';
this.container.style.cursor = 'grab';
} else if (e.altKey && e.shiftKey) {
this.container.style.cursor = 'zoom-out';
} else {
this.container.style.cursor = 'grab';
this.container.style.cursor = 'default';
}
}
}
@@ -91,11 +91,11 @@ export class PanZoomHandler {
return;
}
if (e.altKey && !e.shiftKey) {
this.container.style.cursor = 'zoom-in';
this.container.style.cursor = 'grab';
} else if (e.altKey && e.shiftKey) {
this.container.style.cursor = 'zoom-out';
} else {
this.container.style.cursor = 'grab';
this.container.style.cursor = 'default';
}
}
@@ -118,9 +118,15 @@ export class PanZoomHandler {
}
private handleWheel(e: WheelEvent): void {
// Only zoom when Alt is held (or ctrlKey for pinch-to-zoom gestures)
// ctrlKey is set by browsers for pinch-to-zoom gestures
const isPinchZoom = e.ctrlKey;
if (!e.altKey && !isPinchZoom) {
// Allow normal scrolling when Alt is not held
return;
}
if (isPinchZoom || e.altKey) {
// Pinch gesture or Alt + two-finger drag = zoom
e.preventDefault();
@@ -131,7 +137,9 @@ export class PanZoomHandler {
const mouseY = e.clientY - rect.top;
// Calculate zoom (scroll up = zoom in, scroll down = zoom out)
const delta = -e.deltaY * this.zoomFactor;
// Pinch gestures have smaller deltaY values, so use a higher factor
const effectiveZoomFactor = isPinchZoom ? this.zoomFactor * 5 : this.zoomFactor;
const delta = -e.deltaY * effectiveZoomFactor;
const newScale = Math.min(this.maxScale, Math.max(this.minScale, this.scale * (1 + delta)));
// Zoom toward mouse position
@@ -146,7 +154,7 @@ export class PanZoomHandler {
}
private handleMouseDown(e: MouseEvent): void {
if (e.button !== 0) {
if (e.button !== 0 || !e.altKey) {
return;
}
e.preventDefault();
@@ -182,7 +190,7 @@ export class PanZoomHandler {
private handleMouseUp(): void {
if (this.isPanning) {
this.isPanning = false;
this.container.style.cursor = 'grab';
this.container.style.cursor = 'default';
this.saveState();
}
}
@@ -62,10 +62,6 @@
"command": "_mermaid-chat.resetPanZoom",
"when": "webviewId == 'vscode.chat-mermaid-features.chatOutputItem'"
},
{
"command": "_mermaid-chat.openInEditor",
"when": "webviewId == 'vscode.chat-mermaid-features.chatOutputItem'"
},
{
"command": "_mermaid-chat.copySource",
"when": "webviewId == 'vscode.chat-mermaid-features.chatOutputItem' || webviewId == 'vscode.chat-mermaid-features.preview'"
@@ -77,12 +73,9 @@
"properties": {
"mermaid-chat.enabled": {
"type": "boolean",
"default": false,
"default": true,
"description": "%config.enabled.description%",
"scope": "application",
"tags": [
"experimental"
]
"scope": "application"
}
}
},
@@ -105,6 +105,7 @@ class MermaidChatOutputRenderer implements vscode.ChatOutputRenderer {
opacity: 1;
}
.open-in-editor-btn:hover {
opacity: 1;
background: var(--vscode-toolbar-hoverBackground);
}
</style>
+4 -4
View File
@@ -17,14 +17,14 @@
"contributes": {
"themes": [
{
"id": "2026-light-experimental",
"label": "2026 Light",
"id": "Experimental Light",
"label": "VS Code Light",
"uiTheme": "vs",
"path": "./themes/2026-light.json"
},
{
"id": "2026-dark-experimental",
"label": "2026 Dark",
"id": "Experimental Dark",
"label": "VS Code Dark",
"uiTheme": "vs-dark",
"path": "./themes/2026-dark.json"
}
+21 -8
View File
@@ -6,7 +6,7 @@
"foreground": "#bfbfbf",
"disabledForeground": "#444444",
"errorForeground": "#f48771",
"descriptionForeground": "#888888",
"descriptionForeground": "#999999",
"icon.foreground": "#888888",
"focusBorder": "#3994BCB3",
"textBlockQuote.background": "#242526",
@@ -20,9 +20,6 @@
"button.foreground": "#FFFFFF",
"button.hoverBackground": "#3E9BC4",
"button.border": "#2A2B2CFF",
"button.secondaryBackground": "#242526",
"button.secondaryForeground": "#bfbfbf",
"button.secondaryHoverBackground": "#313233",
"checkbox.background": "#242526",
"checkbox.border": "#333536",
"checkbox.foreground": "#bfbfbf",
@@ -53,9 +50,9 @@
"badge.background": "#3994BC",
"badge.foreground": "#FFFFFF",
"progressBar.background": "#878889",
"list.activeSelectionBackground": "#3994BC26",
"list.activeSelectionBackground": "#3994BC55",
"list.activeSelectionForeground": "#bfbfbf",
"list.inactiveSelectionBackground": "#242526",
"list.inactiveSelectionBackground": "#2C2D2E",
"list.inactiveSelectionForeground": "#bfbfbf",
"list.hoverBackground": "#262728",
"list.hoverForeground": "#bfbfbf",
@@ -71,10 +68,11 @@
"activityBar.foreground": "#bfbfbf",
"activityBar.inactiveForeground": "#888888",
"activityBar.border": "#2A2B2CFF",
"activityBar.activeBorder": "#2A2B2CFF",
"activityBar.activeBorder": "#bfbfbf",
"activityBar.activeFocusBorder": "#3994BCB3",
"activityBarBadge.background": "#3994BC",
"activityBarBadge.foreground": "#FFFFFF",
"activityBarTop.activeBorder": "#bfbfbf",
"sideBar.background": "#191A1B",
"sideBar.foreground": "#bfbfbf",
"sideBar.border": "#2A2B2CFF",
@@ -184,6 +182,7 @@
"tab.border": "#2A2B2CFF",
"tab.lastPinnedBorder": "#2A2B2CFF",
"tab.activeBorder": "#121314",
"tab.activeBorderTop": "#3994BC",
"tab.hoverBackground": "#262728",
"tab.hoverForeground": "#bfbfbf",
"tab.unfocusedActiveBackground": "#121314",
@@ -224,6 +223,9 @@
"quickInputList.focusIconForeground": "#bfbfbf",
"quickInputList.hoverBackground": "#515253",
"terminal.selectionBackground": "#3994BC33",
"terminal.background": "#121314",
"terminal.border": "#2A2B2CFF",
"terminal.tab.activeBorder": "#3994BC00",
"terminalCursor.foreground": "#bfbfbf",
"terminalCursor.background": "#191A1B",
"gitDecoration.addedResourceForeground": "#73c991",
@@ -246,6 +248,8 @@
"gauge.errorBackground": "#F287724D",
"chat.requestBubbleBackground": "#488FAE26",
"chat.requestBubbleHoverBackground": "#488FAE46",
"editorCommentsWidget.rangeBackground": "#488FAE26",
"editorCommentsWidget.rangeActiveBackground": "#488FAE46",
"charts.foreground": "#CCCCCC",
"charts.lines": "#C8CACC80",
"charts.blue": "#57A3F8",
@@ -287,6 +291,15 @@
"foreground": "#C48081"
}
},
{
"name": "Language constants",
"scope": [
"constant.language"
],
"settings": {
"foreground": "#4F8FDD"
}
},
{
"name": "HTML/XML tags",
"scope": [
@@ -536,4 +549,4 @@
"customLiteral": "#DCDCAA",
"numberLiteral": "#b5cea8"
}
}
}
+77 -59
View File
@@ -6,7 +6,7 @@
"foreground": "#202020",
"disabledForeground": "#BBBBBB",
"errorForeground": "#ad0707",
"descriptionForeground": "#666666",
"descriptionForeground": "#555555",
"icon.foreground": "#666666",
"focusBorder": "#0069CCFF",
"textBlockQuote.background": "#EDEDED",
@@ -31,10 +31,10 @@
"dropdown.foreground": "#202020",
"dropdown.listBackground": "#FFFFFF",
"input.background": "#FFFFFF",
"input.border": "#D8D8D880",
"input.border": "#D8D8D866",
"input.foreground": "#202020",
"input.placeholderForeground": "#999999",
"inputOption.activeBackground": "#0069CC33",
"inputOption.activeBackground": "#0069CC26",
"inputOption.activeForeground": "#202020",
"inputOption.activeBorder": "#ECEDEEFF",
"inputValidation.errorBackground": "#FFFFFF",
@@ -46,35 +46,41 @@
"inputValidation.warningBackground": "#FFFFFF",
"inputValidation.warningBorder": "#ECEDEEFF",
"inputValidation.warningForeground": "#202020",
"scrollbar.shadow": "#FFFFFF4D",
"scrollbarSlider.background": "#99999933",
"scrollbarSlider.hoverBackground": "#9999994D",
"scrollbarSlider.activeBackground": "#99999966",
"scrollbar.shadow": "#00000000",
"widget.shadow": "#00000000",
"editorStickyScroll.shadow": "#00000000",
"sideBarStickyScroll.shadow": "#00000000",
"panelStickyScroll.shadow": "#00000000",
"listFilterWidget.shadow": "#00000000",
"scrollbarSlider.background": "#99999926",
"scrollbarSlider.hoverBackground": "#99999940",
"scrollbarSlider.activeBackground": "#99999955",
"badge.background": "#0069CC",
"badge.foreground": "#FFFFFF",
"progressBar.background": "#0069CC",
"list.activeSelectionBackground": "#0069CC26",
"list.activeSelectionBackground": "#0069CC44",
"list.activeSelectionForeground": "#202020",
"list.inactiveSelectionBackground": "#EDEDED",
"list.inactiveSelectionBackground": "#E0E0E0",
"list.inactiveSelectionForeground": "#202020",
"list.hoverBackground": "#F7F7F7",
"list.hoverForeground": "#202020",
"list.dropBackground": "#0069CC1A",
"list.focusBackground": "#0069CC26",
"list.dropBackground": "#0069CC15",
"list.focusBackground": "#0069CC1A",
"list.focusForeground": "#202020",
"list.focusOutline": "#0069CCFF",
"list.highlightForeground": "#0069CC",
"list.invalidItemForeground": "#BBBBBB",
"list.errorForeground": "#ad0707",
"list.warningForeground": "#667309",
"activityBar.background": "#FFFFFF",
"activityBar.background": "#E8ECF2",
"activityBar.foreground": "#202020",
"activityBar.inactiveForeground": "#666666",
"activityBar.border": "#ECEDEEFF",
"activityBar.activeBorder": "#ECEDEEFF",
"activityBar.activeBorder": "#000000",
"activityBar.activeFocusBorder": "#0069CCFF",
"activityBarBadge.background": "#0069CC",
"activityBarBadge.foreground": "#FFFFFF",
"activityBarTop.activeBorder": "#000000",
"sideBar.background": "#FFFFFF",
"sideBar.foreground": "#202020",
"sideBar.border": "#ECEDEEFF",
@@ -82,7 +88,7 @@
"sideBarSectionHeader.background": "#FFFFFF",
"sideBarSectionHeader.foreground": "#202020",
"sideBarSectionHeader.border": "#ECEDEEFF",
"titleBar.activeBackground": "#FFFFFF",
"titleBar.activeBackground": "#E8ECF2",
"titleBar.activeForeground": "#424242",
"titleBar.inactiveBackground": "#FFFFFF",
"titleBar.inactiveForeground": "#666666",
@@ -91,62 +97,61 @@
"menubar.selectionForeground": "#202020",
"menu.background": "#FFFFFF",
"menu.foreground": "#202020",
"menu.selectionBackground": "#0069CC26",
"menu.selectionBackground": "#0069CC1A",
"menu.selectionForeground": "#202020",
"menu.separatorBackground": "#F7F7F7",
"menu.border": "#ECEDEEFF",
"commandCenter.foreground": "#202020",
"commandCenter.activeForeground": "#202020",
"commandCenter.background": "#FFFFFF",
"commandCenter.activeBackground": "#FFFFFFCC",
"commandCenter.border": "#D8D8D880",
"editor.background": "#FAFAFA",
"commandCenter.background": "#E8ECF2",
"commandCenter.activeBackground": "#E8ECF2B3",
"commandCenter.border": "#D8D8D866",
"editor.background": "#FFFFFF",
"editor.foreground": "#202020",
"editorLineNumber.foreground": "#666666",
"editorLineNumber.activeForeground": "#202020",
"editorCursor.foreground": "#202020",
"editor.selectionBackground": "#0069CC26",
"editor.inactiveSelectionBackground": "#0069CC26",
"editor.selectionHighlightBackground": "#0069CC1A",
"editor.wordHighlightBackground": "#0069CC33",
"editor.wordHighlightStrongBackground": "#0069CC33",
"editor.findMatchBackground": "#0069CC4D",
"editor.findMatchHighlightBackground": "#0069CC26",
"editor.selectionBackground": "#0069CC1A",
"editor.inactiveSelectionBackground": "#0069CC1A",
"editor.selectionHighlightBackground": "#0069CC15",
"editor.wordHighlightBackground": "#0069CC26",
"editor.wordHighlightStrongBackground": "#0069CC26",
"editor.findMatchBackground": "#0069CC40",
"editor.findMatchHighlightBackground": "#0069CC1A",
"editor.findRangeHighlightBackground": "#EDEDED",
"editor.hoverHighlightBackground": "#EDEDED",
"editor.lineHighlightBackground": "#EDEDED55",
"editor.lineHighlightBackground": "#EDEDED40",
"editor.rangeHighlightBackground": "#EDEDED",
"editorLink.activeForeground": "#0069CC",
"editorWhitespace.foreground": "#6666664D",
"editorIndentGuide.background": "#F7F7F74D",
"editorWhitespace.foreground": "#66666640",
"editorIndentGuide.background": "#F7F7F740",
"editorIndentGuide.activeBackground": "#F7F7F7",
"editorRuler.foreground": "#F7F7F7",
"editorCodeLens.foreground": "#666666",
"editorBracketMatch.background": "#0069CC55",
"editorBracketMatch.background": "#0069CC40",
"editorBracketMatch.border": "#ECEDEEFF",
"editorWidget.background": "#FFFFFF99",
"editorWidget.background": "#E8ECF2E6",
"editorWidget.border": "#ECEDEEFF",
"editorWidget.foreground": "#202020",
"editorSuggestWidget.background": "#FFFFFF",
"editorSuggestWidget.background": "#E8ECF2E6",
"editorSuggestWidget.border": "#ECEDEEFF",
"editorSuggestWidget.foreground": "#202020",
"editorSuggestWidget.highlightForeground": "#0069CC",
"editorSuggestWidget.selectedBackground": "#0069CC26",
"editorHoverWidget.background": "#FFFFFF",
"editorHoverWidget.background": "#E8ECF2E6",
"editorHoverWidget.border": "#ECEDEEFF",
"peekView.border": "#0069CC",
"peekViewEditor.background": "#FFFFFF",
"peekViewEditor.background": "#E8ECF2E6",
"peekViewEditor.matchHighlightBackground": "#0069CC33",
"peekViewResult.background": "#FFFFFF",
"peekViewResult.background": "#E8ECF2E6",
"peekViewResult.fileForeground": "#202020",
"peekViewResult.lineForeground": "#666666",
"peekViewResult.matchHighlightBackground": "#0069CC33",
"peekViewResult.selectionBackground": "#0069CC26",
"peekViewResult.selectionForeground": "#202020",
"peekViewTitle.background": "#FFFFFF",
"peekViewTitle.background": "#E8ECF2E6",
"peekViewTitleDescription.foreground": "#666666",
"peekViewTitleLabel.foreground": "#202020",
"editorGutter.background": "#FAFAFA",
"editorGutter.addedBackground": "#587c0c",
"editorGutter.deletedBackground": "#ad0707",
"diffEditor.insertedTextBackground": "#587c0c26",
@@ -158,18 +163,19 @@
"editorOverviewRuler.deletedForeground": "#ad0707",
"editorOverviewRuler.errorForeground": "#ad0707",
"editorOverviewRuler.warningForeground": "#667309",
"editorGutter.background": "#FFFFFF",
"panel.background": "#FFFFFF",
"panel.border": "#ECEDEEFF",
"panelTitle.activeBorder": "#0069CC",
"panel.border": "#00000000",
"panelTitle.activeBorder": "#000000",
"panelTitle.activeForeground": "#202020",
"panelTitle.inactiveForeground": "#666666",
"statusBar.background": "#FFFFFF",
"statusBar.background": "#E8ECF2",
"statusBar.foreground": "#666666",
"statusBar.border": "#ECEDEEFF",
"statusBar.border": "#00000000",
"statusBar.focusBorder": "#0069CCFF",
"statusBar.debuggingBackground": "#0069CC",
"statusBar.debuggingForeground": "#FFFFFF",
"statusBar.noFolderBackground": "#FFFFFF",
"statusBar.noFolderBackground": "#E8ECF2",
"statusBar.noFolderForeground": "#666666",
"statusBarItem.activeBackground": "#E6E6E6",
"statusBarItem.hoverBackground": "#F7F7F7",
@@ -177,32 +183,33 @@
"statusBarItem.prominentBackground": "#0069CCDD",
"statusBarItem.prominentForeground": "#FFFFFF",
"statusBarItem.prominentHoverBackground": "#0069CC",
"tab.activeBackground": "#FAFAFA",
"tab.activeBackground": "#FFFFFF",
"tab.activeForeground": "#202020",
"tab.inactiveBackground": "#FFFFFF",
"tab.inactiveForeground": "#666666",
"tab.border": "#ECEDEEFF",
"tab.lastPinnedBorder": "#ECEDEEFF",
"tab.activeBorder": "#FAFAFA",
"tab.activeBorder": "#FFFFFF",
"tab.activeBorderTop": "#000000",
"tab.hoverBackground": "#F7F7F7",
"tab.hoverForeground": "#202020",
"tab.unfocusedActiveBackground": "#FAFAFA",
"tab.unfocusedActiveBackground": "#FFFFFF",
"tab.unfocusedActiveForeground": "#666666",
"tab.unfocusedInactiveBackground": "#FFFFFF",
"tab.unfocusedInactiveForeground": "#BBBBBB",
"editorGroupHeader.tabsBackground": "#FFFFFF",
"editorGroupHeader.tabsBorder": "#ECEDEEFF",
"breadcrumb.foreground": "#666666",
"breadcrumb.background": "#FAFAFA",
"breadcrumb.background": "#FFFFFF",
"breadcrumb.focusForeground": "#202020",
"breadcrumb.activeSelectionForeground": "#202020",
"breadcrumbPicker.background": "#FFFFFF",
"breadcrumbPicker.background": "#E8ECF2E6",
"notificationCenter.border": "#ECEDEEFF",
"notificationCenterHeader.foreground": "#202020",
"notificationCenterHeader.background": "#FFFFFF",
"notificationCenterHeader.background": "#E8ECF2E6",
"notificationToast.border": "#ECEDEEFF",
"notifications.foreground": "#202020",
"notifications.background": "#FFFFFF",
"notifications.background": "#E8ECF2E6",
"notifications.border": "#ECEDEEFF",
"notificationLink.foreground": "#0069CC",
"notificationsWarningIcon.foreground": "#B69500",
@@ -217,13 +224,13 @@
"extensionButton.prominentHoverBackground": "#0064CC",
"pickerGroup.border": "#ECEDEEFF",
"pickerGroup.foreground": "#202020",
"quickInput.background": "#FFFFFF",
"quickInput.background": "#E8ECF2E6",
"quickInput.foreground": "#202020",
"quickInputList.focusBackground": "#0069CC26",
"quickInputList.focusBackground": "#0069CC1A",
"quickInputList.focusForeground": "#202020",
"quickInputList.focusIconForeground": "#202020",
"quickInputList.hoverBackground": "#E7E7E7",
"terminal.selectionBackground": "#0069CC33",
"quickInputList.hoverBackground": "#EDF0F5E6",
"terminal.selectionBackground": "#0069CC26",
"terminalCursor.foreground": "#202020",
"terminalCursor.background": "#FFFFFF",
"gitDecoration.addedResourceForeground": "#587c0c",
@@ -234,21 +241,23 @@
"gitDecoration.conflictingResourceForeground": "#ad0707",
"gitDecoration.stageModifiedResourceForeground": "#667309",
"gitDecoration.stageDeletedResourceForeground": "#ad0707",
"commandCenter.activeBorder": "#D8D8D8CC",
"commandCenter.activeBorder": "#D8D8D8A6",
"quickInput.border": "#D8D8D8",
"gauge.foreground": "#0069CC",
"gauge.background": "#0069CC4D",
"gauge.background": "#0069CC40",
"gauge.border": "#ECEDEEFF",
"gauge.warningForeground": "#B69500",
"gauge.warningBackground": "#B695004D",
"gauge.warningBackground": "#B6950040",
"gauge.errorForeground": "#ad0707",
"gauge.errorBackground": "#ad07074D",
"gauge.errorBackground": "#ad070740",
"statusBarItem.prominentHoverForeground": "#FFFFFF",
"quickInputTitle.background": "#FFFFFF",
"quickInputTitle.background": "#E8ECF2E6",
"chat.requestBubbleBackground": "#EEF4FB",
"chat.requestBubbleHoverBackground": "#E6EDFA",
"editorCommentsWidget.rangeBackground": "#EEF4FB",
"editorCommentsWidget.rangeActiveBackground": "#E6EDFA",
"charts.foreground": "#202020",
"charts.lines": "#20202080",
"charts.lines": "#20202066",
"charts.blue": "#1A5CFF",
"charts.red": "#ad0707",
"charts.yellow": "#667309",
@@ -288,6 +297,15 @@
"foreground": "#B86855"
}
},
{
"name": "Language constants",
"scope": [
"constant.language"
],
"settings": {
"foreground": "#5460C1"
}
},
{
"name": "HTML/XML tags",
"scope": [
+45 -5
View File
@@ -38,10 +38,12 @@
.monaco-workbench .part.auxiliarybar { box-shadow: var(--shadow-md); z-index: 40; position: relative; }
/* Ensure iframe containers in pane-body render above sidebar z-index */
/* Commented out - may cause content to be hidden by z-index issues
.monaco-workbench > div[data-keybinding-context],
.monaco-workbench > div[data-keybinding-context] {
z-index: 50 !important;
}
*/
/* Ensure webview containers render above sidebar z-index */
.monaco-workbench .part.sidebar .webview,
@@ -74,17 +76,37 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active {
box-shadow: inset var(--shadow-active-tab);
position: relative; z-index: 5;
/* border-radius: var(--radius-sm) var(--radius-sm) 0 0; */
border-radius: 0;
border-top: none !important;
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--vscode-focusBorder) 10%, transparent) 0%,
transparent 100%
), var(--vscode-tab-activeBackground) !important;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover:not(.active) {
box-shadow: var(--shadow-sm);
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover:not(.active) { box-shadow: var(--shadow-sm); }
/* Tab border bottom - make transparent */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-and-actions-container { --tabs-border-bottom-color: transparent !important; }
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab { --tab-border-bottom-color: transparent !important; }
/* Title Bar */
.monaco-workbench .part.titlebar { box-shadow: var(--shadow-md); z-index: 60; position: relative; overflow: visible !important; }
.monaco-workbench.vs .part.titlebar { box-shadow: var(--shadow-md); }
.monaco-workbench.vs-dark .part.titlebar {
position: relative;
overflow: visible !important;
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--vscode-focusBorder) 10%, transparent) 0%,
transparent 100%
), var(--vscode-titleBar-activeBackground) !important;
}
.monaco-workbench .part.titlebar.inactive {
background: var(--vscode-titleBar-inactiveBackground) !important;
}
.monaco-workbench .part.titlebar .titlebar-container,
.monaco-workbench .part.titlebar .titlebar-center,
.monaco-workbench .part.titlebar .titlebar-center .window-title,
@@ -281,6 +303,12 @@
color: var(--vscode-icon-foreground) !important;
}
/* Chat input toolbar icons should use proper foreground color, not the muted icon.foreground */
.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;
}
/* Buttons */
.monaco-workbench .monaco-button { box-shadow: var(--shadow-xs); }
.monaco-workbench .monaco-button:hover { box-shadow: var(--shadow-sm); }
@@ -313,13 +341,19 @@
.monaco-workbench .monaco-dropdown .dropdown-menu { box-shadow: var(--shadow-lg); border: none; border-radius: var(--radius-lg); }
/* Terminal */
.monaco-workbench .pane-body.integrated-terminal { box-shadow: var(--shadow-inset-white); }
.monaco-workbench.vs .pane-body.integrated-terminal { box-shadow: var(--shadow-inset-white); }
/* SCM */
.monaco-workbench .scm-view .scm-provider { box-shadow: var(--shadow-sm); border-radius: var(--radius-md); }
/* Debug Toolbar */
.monaco-workbench .debug-toolbar { box-shadow: var(--shadow-lg); border: none; border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-lg) !important; -webkit-backdrop-filter: var(--backdrop-blur-lg) !important; }
.monaco-workbench .debug-toolbar {
box-shadow: var(--shadow-lg);
border: none;
border-radius: var(--radius-lg);
backdrop-filter: var(--backdrop-blur-lg) !important;
-webkit-backdrop-filter: var(--backdrop-blur-lg) !important;
}
.monaco-workbench .debug-hover-widget {
box-shadow: var(--shadow-hover);
@@ -430,6 +464,12 @@
border-top-width: 0;
}
/* Quick Input List - use descriptionForeground color for descriptions */
.monaco-workbench .quick-input-list .monaco-icon-label .label-description {
opacity: 1;
color: var(--vscode-descriptionForeground);
}
/* Remove Borders */
.monaco-workbench.vs .part.sidebar { border-right: none !important; border-left: none !important; }
.monaco-workbench.vs .part.auxiliarybar { border-right: none !important; border-left: none !important; }
+1 -1
View File
@@ -32,7 +32,7 @@
"flags": "i"
},
"increaseIndentPattern": {
"pattern": "^\\s*((If|ElseIf).*Then(?!.*End\\s+If)\\s*(('|REM).*)?|(Else|While|For|Do|Select\\s+Case|Case|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Try|Catch|Finally|SyncLock|Using|Property|Get|Set|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\\b(?!.*\\bEnd\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\\b).*(('|REM).*)?)$",
"pattern": "^\\s*((If|ElseIf)\\b.*\\bThen\\s*(('|REM).*)?|(Else|While|For|Do|Select\\s+Case|Case|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Try|Catch|Finally|SyncLock|Using|Property|Get|Set|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\\b(?!.*\\bEnd\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\\b).*(('|REM).*)?)$",
"flags": "i"
}
},
+12 -12
View File
@@ -15,7 +15,7 @@
"@microsoft/1ds-post-js": "^3.2.13",
"@parcel/watcher": "^2.5.6",
"@types/semver": "^7.5.8",
"@vscode/codicons": "^0.0.45-0",
"@vscode/codicons": "^0.0.45-4",
"@vscode/deviceid": "^0.1.1",
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/native-watchdog": "^1.4.6",
@@ -48,7 +48,7 @@
"minimist": "^1.2.8",
"native-is-elevated": "0.9.0",
"native-keymap": "^3.3.5",
"node-pty": "^1.2.0-beta.8",
"node-pty": "^1.2.0-beta.10",
"open": "^10.1.2",
"tas-client": "0.3.1",
"undici": "^7.18.2",
@@ -149,7 +149,7 @@
"source-map": "0.6.1",
"source-map-support": "^0.3.2",
"style-loader": "^3.3.2",
"tar": "^7.5.4",
"tar": "^7.5.7",
"ts-loader": "^9.5.1",
"tsec": "0.2.7",
"tslib": "^2.6.3",
@@ -2947,9 +2947,9 @@
]
},
"node_modules/@vscode/codicons": {
"version": "0.0.45-0",
"resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45-0.tgz",
"integrity": "sha512-ixvw4auQobMOnMX9cOk8/3GfEgkTKCchsab2O6QvyL6+x4FJegOrK3Wgn4Y+Qua51LqnAsgpB5n74q8HEPh1pA==",
"version": "0.0.45-4",
"resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45-4.tgz",
"integrity": "sha512-uuWqpry+FcHAw1JDkXwEW0YIuTtX3n6KqSshNlvLUjuP92PSrfq99jW52AWJ7qeunmPvgKCaZOeSSLUqHRHjmw==",
"license": "CC-BY-4.0"
},
"node_modules/@vscode/deviceid": {
@@ -12876,9 +12876,9 @@
}
},
"node_modules/node-pty": {
"version": "1.2.0-beta.8",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.8.tgz",
"integrity": "sha512-2gDjTGB/VaMV8cmFMg0d7IfLcWkxtyekn9VSqpq+tUOiu5+nnLfVXYHZbjZCq1kXhnxbdlBjaKLvvVWIbFkicw==",
"version": "1.2.0-beta.10",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.10.tgz",
"integrity": "sha512-vONwSCtAiOVNxeaP/lzDdRw733Q6uB/ELOCFM8DUfKMw6rTFovwFCuvqr9usya7JXV2pfaers3EwuzZfv0QtwA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -16377,9 +16377,9 @@
}
},
"node_modules/tar": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
"integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.109.0",
"distro": "1468752e41ea530021336a556d31c13ff82e02b9",
"distro": "6c9f72a1ba8565301b303ec4314f5a24d585f012",
"author": {
"name": "Microsoft Corporation"
},
@@ -33,7 +33,7 @@
"watch-extensions": "npm run gulp watch-extensions watch-extension-media",
"watch-extensionsd": "deemon npm run watch-extensions",
"kill-watch-extensionsd": "deemon --kill npm run watch-extensions",
"precommit": "node build/hygiene.ts",
"precommit": "node --experimental-strip-types build/hygiene.ts",
"gulp": "node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js",
"electron": "node build/lib/electron.ts",
"7z": "7z",
@@ -77,7 +77,7 @@
"@microsoft/1ds-post-js": "^3.2.13",
"@parcel/watcher": "^2.5.6",
"@types/semver": "^7.5.8",
"@vscode/codicons": "^0.0.45-0",
"@vscode/codicons": "^0.0.45-4",
"@vscode/deviceid": "^0.1.1",
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/native-watchdog": "^1.4.6",
@@ -110,7 +110,7 @@
"minimist": "^1.2.8",
"native-is-elevated": "0.9.0",
"native-keymap": "^3.3.5",
"node-pty": "^1.2.0-beta.8",
"node-pty": "^1.2.0-beta.10",
"open": "^10.1.2",
"tas-client": "0.3.1",
"undici": "^7.18.2",
@@ -211,7 +211,7 @@
"source-map": "0.6.1",
"source-map-support": "^0.3.2",
"style-loader": "^3.3.2",
"tar": "^7.5.4",
"tar": "^7.5.7",
"ts-loader": "^9.5.1",
"tsec": "0.2.7",
"tslib": "^2.6.3",
+4 -4
View File
@@ -38,7 +38,7 @@
"katex": "^0.16.22",
"kerberos": "2.1.1",
"minimist": "^1.2.8",
"node-pty": "^1.2.0-beta.8",
"node-pty": "^1.2.0-beta.10",
"tas-client": "0.3.1",
"vscode-oniguruma": "1.7.0",
"vscode-regexpp": "^3.1.0",
@@ -1053,9 +1053,9 @@
}
},
"node_modules/node-pty": {
"version": "1.2.0-beta.8",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.8.tgz",
"integrity": "sha512-2gDjTGB/VaMV8cmFMg0d7IfLcWkxtyekn9VSqpq+tUOiu5+nnLfVXYHZbjZCq1kXhnxbdlBjaKLvvVWIbFkicw==",
"version": "1.2.0-beta.10",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.10.tgz",
"integrity": "sha512-vONwSCtAiOVNxeaP/lzDdRw733Q6uB/ELOCFM8DUfKMw6rTFovwFCuvqr9usya7JXV2pfaers3EwuzZfv0QtwA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
+1 -1
View File
@@ -33,7 +33,7 @@
"katex": "^0.16.22",
"kerberos": "2.1.1",
"minimist": "^1.2.8",
"node-pty": "^1.2.0-beta.8",
"node-pty": "^1.2.0-beta.10",
"tas-client": "0.3.1",
"vscode-oniguruma": "1.7.0",
"vscode-regexpp": "^3.1.0",
+4 -4
View File
@@ -10,7 +10,7 @@
"dependencies": {
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@vscode/codicons": "^0.0.45-0",
"@vscode/codicons": "^0.0.45-4",
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/tree-sitter-wasm": "^0.3.0",
"@vscode/vscode-languagedetection": "1.0.21",
@@ -73,9 +73,9 @@
"integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ=="
},
"node_modules/@vscode/codicons": {
"version": "0.0.45-0",
"resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45-0.tgz",
"integrity": "sha512-ixvw4auQobMOnMX9cOk8/3GfEgkTKCchsab2O6QvyL6+x4FJegOrK3Wgn4Y+Qua51LqnAsgpB5n74q8HEPh1pA==",
"version": "0.0.45-4",
"resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45-4.tgz",
"integrity": "sha512-uuWqpry+FcHAw1JDkXwEW0YIuTtX3n6KqSshNlvLUjuP92PSrfq99jW52AWJ7qeunmPvgKCaZOeSSLUqHRHjmw==",
"license": "CC-BY-4.0"
},
"node_modules/@vscode/iconv-lite-umd": {
+1 -1
View File
@@ -5,7 +5,7 @@
"dependencies": {
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@vscode/codicons": "^0.0.45-0",
"@vscode/codicons": "^0.0.45-4",
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/tree-sitter-wasm": "^0.3.0",
"@vscode/vscode-languagedetection": "1.0.21",
@@ -19,6 +19,7 @@
"workbench.sideBar.location": "right",
"workbench.statusBar.visible": false,
"workbench.secondarySideBar.forceMaximized": true,
"workbench.secondarySideBar.defaultVisibility": "maximized",
"workbench.startupEditor": "none",
"workbench.tips.enabled": false,
"workbench.layoutControl.type": "toggles",
+30
View File
@@ -2804,3 +2804,33 @@ function setOrRemoveAttribute(element: HTMLOrSVGElement, key: string, value: unk
type ElementAttributeKeys<T> = Partial<{
[K in keyof T]: T[K] extends Function ? never : T[K] extends object ? ElementAttributeKeys<T[K]> : Value<number | T[K] | undefined | null>;
}>;
/**
* A custom element that fires callbacks when connected to or disconnected from the DOM.
* Useful for tracking whether a template or component is currently mounted, especially
* with iframes/webviews that are sensitive to movement.
*
* @example
* ```ts
* const observer = document.createElement('connection-observer') as ConnectionObserverElement;
* observer.onDidConnect = () => console.log('mounted');
* observer.onDidDisconnect = () => console.log('unmounted');
* container.appendChild(observer);
* ```
*/
export class ConnectionObserverElement extends HTMLElement {
public onDidConnect?: () => void;
public onDidDisconnect?: () => void;
disconnectedCallback() {
this.onDidDisconnect?.();
}
connectedCallback() {
this.onDidConnect?.();
}
}
if (!customElements.get('connection-observer')) {
customElements.define('connection-observer', ConnectionObserverElement);
}
+11 -1
View File
@@ -48,6 +48,7 @@ export interface IButtonStyles {
readonly buttonSecondaryBackground: string | undefined;
readonly buttonSecondaryHoverBackground: string | undefined;
readonly buttonSecondaryForeground: string | undefined;
readonly buttonSecondaryBorder: string | undefined;
readonly buttonBorder: string | undefined;
}
@@ -59,7 +60,8 @@ export const unthemedButtonStyles: IButtonStyles = {
buttonBorder: undefined,
buttonSecondaryBackground: undefined,
buttonSecondaryForeground: undefined,
buttonSecondaryHoverBackground: undefined
buttonSecondaryHoverBackground: undefined,
buttonSecondaryBorder: undefined
};
export interface IButton extends IDisposable {
@@ -120,9 +122,13 @@ export class Button extends Disposable implements IButton {
this._element.classList.toggle('small', !!options.small);
const background = options.secondary ? options.buttonSecondaryBackground : options.buttonBackground;
const foreground = options.secondary ? options.buttonSecondaryForeground : options.buttonForeground;
const border = options.secondary ? options.buttonSecondaryBorder : options.buttonBorder;
this._element.style.color = foreground || '';
this._element.style.backgroundColor = background || '';
if (border) {
this._element.style.border = `1px solid ${border}`;
}
if (options.supportShortLabel) {
this._labelShortElement = document.createElement('div');
@@ -223,16 +229,20 @@ export class Button extends Disposable implements IButton {
private updateStyles(hover: boolean): void {
let background;
let foreground;
let border;
if (this.options.secondary) {
background = hover ? this.options.buttonSecondaryHoverBackground : this.options.buttonSecondaryBackground;
foreground = this.options.buttonSecondaryForeground;
border = this.options.buttonSecondaryBorder;
} else {
background = hover ? this.options.buttonHoverBackground : this.options.buttonBackground;
foreground = this.options.buttonForeground;
border = this.options.buttonBorder;
}
this._element.style.backgroundColor = background || '';
this._element.style.color = foreground || '';
this._element.style.border = border ? `1px solid ${border}` : '';
}
get element(): HTMLElement {
+1
View File
@@ -173,6 +173,7 @@ export class Toggle extends Widget {
this.checked = !this._checked;
this._onChange.fire(false);
ev.preventDefault();
ev.stopPropagation();
}
});
+2
View File
@@ -652,4 +652,6 @@ export const codiconsLibrary = {
worktree: register('worktree', 0xec7e),
screenCut: register('screen-cut', 0xec7f),
ask: register('ask', 0xec80),
openai: register('openai', 0xec81),
claude: register('claude', 0xec82),
} as const;
-1
View File
@@ -59,5 +59,4 @@ export interface IDefaultAccount {
readonly sessionId: string;
readonly enterprise: boolean;
readonly entitlementsData?: IEntitlementsData | null;
readonly policyData?: IPolicyData;
}
+2 -2
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from '../../nls.js';
import { IDefaultAccount } from './defaultAccount.js';
import { IPolicyData } from './defaultAccount.js';
/**
* System-wide policy file path for Linux systems.
@@ -96,5 +96,5 @@ export interface IPolicy {
*
* If `undefined`, the feature's setting is not locked and can be overridden by other means.
*/
readonly value?: (account: IDefaultAccount) => string | number | boolean | undefined;
readonly value?: (policyData: IPolicyData) => string | number | boolean | undefined;
}
+1
View File
@@ -205,6 +205,7 @@ export interface IProductConfiguration {
readonly hasPrereleaseVersion?: boolean;
readonly excludeVersionRange?: string;
}>;
readonly extensionsForceVersionByQuality?: readonly string[];
readonly msftInternalDomains?: string[];
readonly linkProtectionTrustedDomains?: readonly string[];
@@ -24,14 +24,7 @@
function showSplash(configuration: INativeWindowConfiguration) {
performance.mark('code/willShowPartsSplash');
const isAgentSessionsWindow = configuration.profiles?.profile?.id === 'agent-sessions';
if (isAgentSessionsWindow) {
showAgentSessionsSplash(configuration);
} else {
showDefaultSplash(configuration);
}
showDefaultSplash(configuration);
performance.mark('code/didShowPartsSplash');
}
@@ -278,63 +271,6 @@
}
}
function showAgentSessionsSplash(configuration: INativeWindowConfiguration) {
// Agent sessions windows render a very opinionated splash:
// - Dark theme background (agent sessions use 2026-dark-experimental)
// - Title bar only for window controls
// - Secondary sidebar takes all remaining space (maximized)
// - No status bar, no activity bar, no sidebar
const baseTheme = 'vs-dark';
const shellBackground = '#191A1B'; // 2026-dark-experimental sidebar background
const shellForeground = '#CCCCCC';
// Apply base colors
const style = document.createElement('style');
style.className = 'initialShellColors';
window.document.head.appendChild(style);
style.textContent = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`;
// Set zoom level from splash data if available
if (typeof configuration.partsSplash?.zoomLevel === 'number' && typeof preloadGlobals?.webFrame?.setZoomLevel === 'function') {
preloadGlobals.webFrame.setZoomLevel(configuration.partsSplash.zoomLevel);
}
const splash = document.createElement('div');
splash.id = 'monaco-parts-splash';
splash.className = baseTheme;
// Title bar height - use stored value or default
const titleBarHeight = configuration.partsSplash?.layoutInfo?.titleBarHeight ?? 35;
// Title bar for window dragging
if (titleBarHeight > 0) {
const titleDiv = document.createElement('div');
titleDiv.style.position = 'absolute';
titleDiv.style.width = '100%';
titleDiv.style.height = `${titleBarHeight}px`;
titleDiv.style.left = '0';
titleDiv.style.top = '0';
titleDiv.style.backgroundColor = shellBackground;
(titleDiv.style as CSSStyleDeclaration & { '-webkit-app-region': string })['-webkit-app-region'] = 'drag';
splash.appendChild(titleDiv);
}
// Secondary sidebar (maximized, takes all remaining space)
// This is the main content area for agent sessions
const auxSideDiv = document.createElement('div');
auxSideDiv.style.position = 'absolute';
auxSideDiv.style.width = '100%';
auxSideDiv.style.height = `calc(100% - ${titleBarHeight}px)`;
auxSideDiv.style.top = `${titleBarHeight}px`;
auxSideDiv.style.left = '0';
auxSideDiv.style.backgroundColor = shellBackground;
splash.appendChild(auxSideDiv);
window.document.body.appendChild(splash);
}
//#endregion
//#region Window Helpers
+5 -5
View File
@@ -314,27 +314,27 @@ class CliMain extends Disposable {
// List Extensions
if (this.argv['list-extensions']) {
return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).listExtensions(!!this.argv['show-versions'], this.argv['category'], profileLocation);
return instantiationService.createInstance(ExtensionManagementCLI, [], new ConsoleLogger(LogLevel.Info, false)).listExtensions(!!this.argv['show-versions'], this.argv['category'], profileLocation);
}
// Install Extension
else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) {
const installOptions: InstallOptions = { isMachineScoped: !!this.argv['do-not-sync'], installPreReleaseVersion: !!this.argv['pre-release'], donotIncludePackAndDependencies: !!this.argv['do-not-include-pack-dependencies'], profileLocation };
return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.asExtensionIdOrVSIX(this.argv['install-builtin-extension'] || []), installOptions, !!this.argv['force']);
return instantiationService.createInstance(ExtensionManagementCLI, [], new ConsoleLogger(LogLevel.Info, false)).installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.asExtensionIdOrVSIX(this.argv['install-builtin-extension'] || []), installOptions, !!this.argv['force']);
}
// Uninstall Extension
else if (this.argv['uninstall-extension']) {
return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force'], profileLocation);
return instantiationService.createInstance(ExtensionManagementCLI, [], new ConsoleLogger(LogLevel.Info, false)).uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force'], profileLocation);
}
else if (this.argv['update-extensions']) {
return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).updateExtensions(profileLocation);
return instantiationService.createInstance(ExtensionManagementCLI, [], new ConsoleLogger(LogLevel.Info, false)).updateExtensions(profileLocation);
}
// Locate Extension
else if (this.argv['locate-extension']) {
return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).locateExtension(this.argv['locate-extension']);
return instantiationService.createInstance(ExtensionManagementCLI, [], new ConsoleLogger(LogLevel.Info, false)).locateExtension(this.argv['locate-extension']);
}
// Install MCP server
@@ -37,6 +37,7 @@ import { EditorWorkerHost } from '../../common/services/editorWorkerHost.js';
import { StringEdit } from '../../common/core/edits/stringEdit.js';
import { OffsetRange } from '../../common/core/ranges/offsetRange.js';
import { FileAccess } from '../../../base/common/network.js';
import { isCompletionsEnabledWithTextResourceConfig } from '../../common/services/completionsEnablement.js';
/**
* Stop the worker if it was not needed for 5 min.
@@ -280,7 +281,9 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide
return undefined;
}
if (config.wordBasedSuggestions === 'offWithInlineSuggestions' && this.languageFeaturesService.inlineCompletionsProvider.has(model)) {
if (config.wordBasedSuggestions === 'offWithInlineSuggestions'
&& this.languageFeaturesService.inlineCompletionsProvider.has(model)
&& isCompletionsEnabledWithTextResourceConfig(this._configurationService, model.uri, model.getLanguageId())) {
return undefined;
}
@@ -1300,7 +1300,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
reason = source;
sourceStr = source.metadata.source;
} else {
reason = EditSources.unknown({ name: sourceStr });
reason = EditSources.unknown({ name: source });
sourceStr = source;
}
+23 -8
View File
@@ -97,6 +97,7 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
let baseIdx = 0;
let ourIdx = 0;
let offset = 0;
let lastEndEx = -1; // Track end of last added edit to ensure sorted/disjoint invariant
while (ourIdx < this.replacements.length || baseIdx < base.replacements.length) {
// take the edit that starts first
@@ -108,10 +109,17 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
break;
} else if (!baseEdit) {
// no more edits from base
newEdits.push(new StringReplacement(
ourEdit.replaceRange.delta(offset),
ourEdit.newText
));
const transformedRange = ourEdit.replaceRange.delta(offset);
// Check if the transformed edit would violate the sorted/disjoint invariant
if (transformedRange.start < lastEndEx) {
if (noOverlap) {
return undefined;
}
ourIdx++; // Skip this edit as it conflicts with a previously added edit
continue;
}
newEdits.push(new StringReplacement(transformedRange, ourEdit.newText));
lastEndEx = transformedRange.endExclusive;
ourIdx++;
} else if (ourEdit.replaceRange.intersects(baseEdit.replaceRange) || areConcurrentInserts(ourEdit.replaceRange, baseEdit.replaceRange)) {
ourIdx++; // Don't take our edit, as it is conflicting -> skip
@@ -120,10 +128,17 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
}
} else if (ourEdit.replaceRange.start < baseEdit.replaceRange.start) {
// Our edit starts first
newEdits.push(new StringReplacement(
ourEdit.replaceRange.delta(offset),
ourEdit.newText
));
const transformedRange = ourEdit.replaceRange.delta(offset);
// Check if the transformed edit would violate the sorted/disjoint invariant
if (transformedRange.start < lastEndEx) {
if (noOverlap) {
return undefined;
}
ourIdx++; // Skip this edit as it conflicts with a previously added edit
continue;
}
newEdits.push(new StringReplacement(transformedRange, ourEdit.newText));
lastEndEx = transformedRange.endExclusive;
ourIdx++;
} else {
baseIdx++;
@@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import product from '../../../platform/product/common/product.js';
import { isObject } from '../../../base/common/types.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { ITextResourceConfigurationService } from './textResourceConfiguration.js';
import { URI } from '../../../base/common/uri.js';
/**
* Get the completions enablement setting name from product configuration.
*/
function getCompletionsEnablementSettingName(): string | undefined {
return product.defaultChatAgent?.completionsEnablementSetting;
}
/**
* Checks if completions (e.g., Copilot) are enabled for a given language ID
* using `IConfigurationService`.
*
* @param configurationService The configuration service to read settings from.
* @param modeId The language ID to check. Defaults to '*' which checks the global setting.
* @returns `true` if completions are enabled for the language, `false` otherwise.
*/
export function isCompletionsEnabled(configurationService: IConfigurationService, modeId: string = '*'): boolean {
const settingName = getCompletionsEnablementSettingName();
if (!settingName) {
return false;
}
return isCompletionsEnabledFromObject(
configurationService.getValue<Record<string, boolean>>(settingName),
modeId
);
}
/**
* Checks if completions (e.g., Copilot) are enabled for a given language ID
* using `ITextResourceConfigurationService`.
*
* @param configurationService The text resource configuration service to read settings from.
* @param modeId The language ID to check. Defaults to '*' which checks the global setting.
* @returns `true` if completions are enabled for the language, `false` otherwise.
*/
export function isCompletionsEnabledWithTextResourceConfig(configurationService: ITextResourceConfigurationService, resource: URI, modeId: string = '*'): boolean {
const settingName = getCompletionsEnablementSettingName();
if (!settingName) {
return false;
}
// Pass undefined as resource to get the global setting
return isCompletionsEnabledFromObject(
configurationService.getValue<Record<string, boolean>>(resource, settingName),
modeId
);
}
/**
* Checks if completions are enabled for a given language ID using a pre-fetched
* completions enablement object.
*
* @param completionsEnablementObject The object containing per-language enablement settings.
* @param modeId The language ID to check. Defaults to '*' which checks the global setting.
* @returns `true` if completions are enabled for the language, `false` otherwise.
*/
export function isCompletionsEnabledFromObject(completionsEnablementObject: Record<string, boolean> | undefined, modeId: string = '*'): boolean {
if (!isObject(completionsEnablementObject)) {
return false; // default to disabled if setting is not available
}
if (typeof completionsEnablementObject[modeId] !== 'undefined') {
return Boolean(completionsEnablementObject[modeId]); // go with setting if explicitly defined
}
return Boolean(completionsEnablementObject['*']); // fallback to global setting otherwise
}
@@ -28,6 +28,7 @@ import { Command, InlineCompletionEndOfLifeReasonKind, InlineCompletionTriggerKi
import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';
import { ITextModel } from '../../../../common/model.js';
import { offsetEditFromContentChanges } from '../../../../common/model/textModelStringEdit.js';
import { isCompletionsEnabledFromObject } from '../../../../common/services/completionsEnablement.js';
import { IFeatureDebounceInformation } from '../../../../common/services/languageFeatureDebounce.js';
import { IModelContentChangedEvent } from '../../../../common/textModelEvents.js';
import { formatRecordableLogEntry, IRecordableEditorLogEntry, IRecordableLogEntry, StructuredLogger } from '../structuredLogger.js';
@@ -445,7 +446,7 @@ export class InlineCompletionsSource extends Disposable {
}
if (!isCompletionsEnabled(this._completionsEnabled, this._textModel.getLanguageId())) {
if (!isCompletionsEnabledFromObject(this._completionsEnabled, this._textModel.getLanguageId())) {
return;
}
@@ -571,18 +572,6 @@ function isSubset<T>(set1: Set<T>, set2: Set<T>): boolean {
return [...set1].every(item => set2.has(item));
}
function isCompletionsEnabled(completionsEnablementObject: Record<string, boolean> | undefined, modeId: string = '*'): boolean {
if (completionsEnablementObject === undefined) {
return false; // default to disabled if setting is not available
}
if (typeof completionsEnablementObject[modeId] !== 'undefined') {
return Boolean(completionsEnablementObject[modeId]); // go with setting if explicitly defined
}
return Boolean(completionsEnablementObject['*']); // fallback to global setting otherwise
}
class UpdateOperation implements IDisposable {
constructor(
public readonly request: UpdateRequest,
@@ -380,7 +380,7 @@ export class InlineSuggestData {
public async reportInlineEditShown(commandService: ICommandService, updatedInsertText: string, viewKind: InlineCompletionViewKind, viewData: InlineCompletionViewData, editKind: InlineSuggestionEditKind | undefined, timeWhenShown: number): Promise<void> {
this.updateShownDuration(viewKind);
if (this._didShow) {
if (this._didShow || this._didReportEndOfLife) {
return;
}
this.addPerformanceMarker('shown');
@@ -429,6 +429,12 @@ export class InlineSuggestData {
reason = this._lastSetEndOfLifeReason ?? { kind: InlineCompletionEndOfLifeReasonKind.Ignored, userTypingDisagreed: false, supersededBy: undefined };
}
// A suggestion can only be "rejected" if it was actually shown to the user.
// If the suggestion was never shown, downgrade to "ignored".
if (reason.kind === InlineCompletionEndOfLifeReasonKind.Rejected && !this._didShow) {
reason = { kind: InlineCompletionEndOfLifeReasonKind.Ignored, userTypingDisagreed: false, supersededBy: undefined };
}
if (reason.kind === InlineCompletionEndOfLifeReasonKind.Rejected && this.source.provider.handleRejection) {
this.source.provider.handleRejection(this.source.inlineSuggestions, this.sourceInlineCompletion);
}
@@ -8,7 +8,7 @@ import { Color } from '../../../../../../base/common/color.js';
import { BugIndicatingError } from '../../../../../../base/common/errors.js';
import { IObservable, observableFromEventOpts } from '../../../../../../base/common/observable.js';
import { localize } from '../../../../../../nls.js';
import { buttonBackground, buttonForeground, buttonSecondaryBackground, buttonSecondaryForeground, diffInserted, diffInsertedLine, diffRemoved, editorBackground } from '../../../../../../platform/theme/common/colorRegistry.js';
import { buttonBackground, buttonForeground, diffInserted, diffInsertedLine, diffRemoved, editorBackground, editorHoverBackground, editorHoverBorder, editorHoverForeground } from '../../../../../../platform/theme/common/colorRegistry.js';
import { asCssVariable, ColorIdentifier, darken, registerColor, transparent } from '../../../../../../platform/theme/common/colorUtils.js';
import { IThemeService } from '../../../../../../platform/theme/common/themeService.js';
import { InlineCompletionEditorType } from '../../model/provideInlineCompletions.js';
@@ -85,17 +85,17 @@ export const inlineEditIndicatorPrimaryBackground = registerColor(
export const inlineEditIndicatorSecondaryForeground = registerColor(
'inlineEdit.gutterIndicator.secondaryForeground',
buttonSecondaryForeground,
editorHoverForeground,
localize('inlineEdit.gutterIndicator.secondaryForeground', 'Foreground color for the secondary inline edit gutter indicator.')
);
export const inlineEditIndicatorSecondaryBorder = registerColor(
'inlineEdit.gutterIndicator.secondaryBorder',
buttonSecondaryBackground,
editorHoverBorder,
localize('inlineEdit.gutterIndicator.secondaryBorder', 'Border color for the secondary inline edit gutter indicator.')
);
export const inlineEditIndicatorSecondaryBackground = registerColor(
'inlineEdit.gutterIndicator.secondaryBackground',
inlineEditIndicatorSecondaryBorder,
editorHoverBackground,
localize('inlineEdit.gutterIndicator.secondaryBackground', 'Background color for the secondary inline edit gutter indicator.')
);
@@ -267,6 +267,8 @@ export async function withAsyncTestCodeEditorAndInlineCompletionsModel<T>(
options.serviceCollection.set(IDefaultAccountService, {
_serviceBrand: undefined,
onDidChangeDefaultAccount: Event.None,
onDidChangePolicyData: Event.None,
policyData: null,
getDefaultAccount: async () => null,
setDefaultAccountProvider: () => { },
getDefaultAccountAuthenticationProvider: () => { return { id: 'mockProvider', name: 'Mock Provider', enterprise: false }; },
@@ -100,7 +100,7 @@ import { IDataChannelService, NullDataChannelService } from '../../../platform/d
import { IWebWorkerService } from '../../../platform/webWorker/browser/webWorkerService.js';
import { StandaloneWebWorkerService } from './services/standaloneWebWorkerService.js';
import { IDefaultAccountService } from '../../../platform/defaultAccount/common/defaultAccount.js';
import { IDefaultAccount, IDefaultAccountAuthenticationProvider } from '../../../base/common/defaultAccount.js';
import { IDefaultAccount, IDefaultAccountAuthenticationProvider, IPolicyData } from '../../../base/common/defaultAccount.js';
class SimpleModel implements IResolvedTextEditorModel {
@@ -1115,6 +1115,8 @@ class StandaloneDefaultAccountService implements IDefaultAccountService {
declare readonly _serviceBrand: undefined;
readonly onDidChangeDefaultAccount: Event<IDefaultAccount | null> = Event.None;
readonly onDidChangePolicyData: Event<IPolicyData | null> = Event.None;
readonly policyData: IPolicyData | null = null;
async getDefaultAccount(): Promise<IDefaultAccount | null> {
return null;
@@ -154,6 +154,62 @@ suite('Edit', () => {
// This should return undefined because both are inserts at the same position
assert.strictEqual(rebasedEdit, undefined);
});
test('tryRebase should return undefined when rebasing would produce non-disjoint edits (negative offset case)', () => {
// ourEdit1: [100, 110) -> "A"
// ourEdit2: [120, 120) -> "B"
// baseEdit: [110, 125) -> "" (delete 15 chars, offset = -15)
// After transformation, ourEdit2 at [105, 105) < ourEdit1 end (110)
const ourEdit = StringEdit.create([
new StringReplacement(new OffsetRange(100, 110), 'A'),
new StringReplacement(OffsetRange.emptyAt(120), 'B'),
]);
const baseEdit = StringEdit.create([
new StringReplacement(new OffsetRange(110, 125), ''),
]);
const result = ourEdit.tryRebase(baseEdit);
assert.strictEqual(result, undefined);
});
test('tryRebase should succeed when edits remain disjoint after rebasing', () => {
// ourEdit1: [100, 110) -> "A"
// ourEdit2: [200, 210) -> "B"
// baseEdit: [50, 60) -> "" (delete 10 chars, offset = -10)
// After: ourEdit1 at [90, 100), ourEdit2 at [190, 200) - still disjoint
const ourEdit = StringEdit.create([
new StringReplacement(new OffsetRange(100, 110), 'A'),
new StringReplacement(new OffsetRange(200, 210), 'B'),
]);
const baseEdit = StringEdit.create([
new StringReplacement(new OffsetRange(50, 60), ''),
]);
const result = ourEdit.tryRebase(baseEdit);
assert.ok(result);
assert.strictEqual(result?.replacements[0].replaceRange.start, 90);
assert.strictEqual(result?.replacements[1].replaceRange.start, 190);
});
test('rebaseSkipConflicting should skip edits that would produce non-disjoint results', () => {
const ourEdit = StringEdit.create([
new StringReplacement(new OffsetRange(100, 110), 'A'),
new StringReplacement(OffsetRange.emptyAt(120), 'B'),
]);
const baseEdit = StringEdit.create([
new StringReplacement(new OffsetRange(110, 125), ''),
]);
// Should not throw, and should skip the conflicting edit
const result = ourEdit.rebaseSkipConflicting(baseEdit);
assert.strictEqual(result.replacements.length, 1);
assert.strictEqual(result.replacements[0].replaceRange.start, 100);
});
});
suite('ArrayEdit', () => {
@@ -24,7 +24,7 @@ import { ILayoutService } from '../../layout/browser/layoutService.js';
import { IHoverService } from '../../hover/browser/hover.js';
import { MarkdownString } from '../../../base/common/htmlContent.js';
import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js';
import { IHoverWidget } from '../../../base/browser/ui/hover/hover.js';
import { IHoverPositionOptions, IHoverWidget } from '../../../base/browser/ui/hover/hover.js';
export const acceptSelectedActionCommand = 'acceptSelectedCodeAction';
export const previewSelectedActionCommand = 'previewSelectedCodeAction';
@@ -44,6 +44,8 @@ export interface IActionListItemHover {
* Content to display in the hover.
*/
readonly content?: string;
readonly position?: IHoverPositionOptions;
}
export interface IActionListItem<T> {
@@ -479,6 +481,7 @@ export class ActionList<T> extends Disposable {
position: {
hoverPosition: HoverPosition.LEFT,
forcePosition: false,
...element.hover.position,
},
appearance: {
showPointer: true,
@@ -209,6 +209,10 @@
display: flex;
}
.action-widget .monaco-list-row .action-list-item-toolbar .monaco-action-bar:not(.vertical) .action-label:not(.disabled):hover{
background-color: var(--vscode-list-activeSelectionBackground);
}
.action-widget-delegate-label {
display: flex;
align-items: center;
@@ -27,6 +27,7 @@ export interface IBrowserViewState {
canGoForward: boolean;
loading: boolean;
focused: boolean;
visible: boolean;
isDevToolsOpen: boolean;
lastScreenshot: VSBuffer | undefined;
lastFavicon: string | undefined;
@@ -55,6 +56,10 @@ export interface IBrowserViewFocusEvent {
focused: boolean;
}
export interface IBrowserViewVisibilityEvent {
visible: boolean;
}
export interface IBrowserViewDevToolsStateEvent {
isDevToolsOpen: boolean;
}
@@ -112,6 +117,7 @@ export interface IBrowserViewService {
onDynamicDidNavigate(id: string): Event<IBrowserViewNavigationEvent>;
onDynamicDidChangeLoadingState(id: string): Event<IBrowserViewLoadingEvent>;
onDynamicDidChangeFocus(id: string): Event<IBrowserViewFocusEvent>;
onDynamicDidChangeVisibility(id: string): Event<IBrowserViewVisibilityEvent>;
onDynamicDidChangeDevToolsState(id: string): Event<IBrowserViewDevToolsStateEvent>;
onDynamicDidKeyCommand(id: string): Event<IBrowserViewKeyDownEvent>;
onDynamicDidChangeTitle(id: string): Event<IBrowserViewTitleChangeEvent>;
@@ -7,7 +7,7 @@ import { WebContentsView, webContents } from 'electron';
import { Disposable } from '../../../base/common/lifecycle.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewNewPageRequest, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult } from '../common/browserView.js';
import { IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewNewPageRequest, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult, IBrowserViewVisibilityEvent } from '../common/browserView.js';
import { EVENT_KEY_CODE_MAP, KeyCode, KeyMod, SCAN_CODE_STR_TO_EVENT_KEY_CODE } from '../../../base/common/keyCodes.js';
import { IWindowsMainService } from '../../windows/electron-main/windows.js';
import { IBaseWindow, ICodeWindow } from '../../window/electron-main/window.js';
@@ -51,6 +51,9 @@ export class BrowserView extends Disposable {
private readonly _onDidChangeFocus = this._register(new Emitter<IBrowserViewFocusEvent>());
readonly onDidChangeFocus: Event<IBrowserViewFocusEvent> = this._onDidChangeFocus.event;
private readonly _onDidChangeVisibility = this._register(new Emitter<IBrowserViewVisibilityEvent>());
readonly onDidChangeVisibility: Event<IBrowserViewVisibilityEvent> = this._onDidChangeVisibility.event;
private readonly _onDidChangeDevToolsState = this._register(new Emitter<IBrowserViewDevToolsStateEvent>());
readonly onDidChangeDevToolsState: Event<IBrowserViewDevToolsStateEvent> = this._onDidChangeDevToolsState.event;
@@ -281,6 +284,7 @@ export class BrowserView extends Disposable {
canGoForward: webContents.navigationHistory.canGoForward(),
loading: webContents.isLoading(),
focused: webContents.isFocused(),
visible: this._view.getVisible(),
isDevToolsOpen: webContents.isDevToolsOpened(),
lastScreenshot: this._lastScreenshot,
lastFavicon: this._lastFavicon,
@@ -322,12 +326,17 @@ export class BrowserView extends Disposable {
* Set the visibility of this view
*/
setVisible(visible: boolean): void {
if (this._view.getVisible() === visible) {
return;
}
// If the view is focused, pass focus back to the window when hiding
if (!visible && this._view.webContents.isFocused()) {
this._window?.win?.webContents.focus();
}
this._view.setVisible(visible);
this._onDidChangeVisibility.fire({ visible });
}
/**
@@ -123,6 +123,10 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
return this._getBrowserView(id).onDidChangeFocus;
}
onDynamicDidChangeVisibility(id: string) {
return this._getBrowserView(id).onDidChangeVisibility;
}
onDynamicDidChangeDevToolsState(id: string) {
return this._getBrowserView(id).onDidChangeDevToolsState;
}
@@ -5,11 +5,13 @@
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { Event } from '../../../base/common/event.js';
import { IDefaultAccount, IDefaultAccountAuthenticationProvider } from '../../../base/common/defaultAccount.js';
import { IDefaultAccount, IDefaultAccountAuthenticationProvider, IPolicyData } from '../../../base/common/defaultAccount.js';
export interface IDefaultAccountProvider {
readonly defaultAccount: IDefaultAccount | null;
readonly onDidChangeDefaultAccount: Event<IDefaultAccount | null>;
readonly policyData: IPolicyData | null;
readonly onDidChangePolicyData: Event<IPolicyData | null>;
getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider;
refresh(): Promise<IDefaultAccount | null>;
signIn(options?: { additionalScopes?: readonly string[];[key: string]: unknown }): Promise<IDefaultAccount | null>;
@@ -20,6 +22,8 @@ export const IDefaultAccountService = createDecorator<IDefaultAccountService>('d
export interface IDefaultAccountService {
readonly _serviceBrand: undefined;
readonly onDidChangeDefaultAccount: Event<IDefaultAccount | null>;
readonly onDidChangePolicyData: Event<IPolicyData | null>;
readonly policyData: IPolicyData | null;
getDefaultAccount(): Promise<IDefaultAccount | null>;
getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider;
setDefaultAccountProvider(provider: IDefaultAccountProvider): void;
@@ -54,7 +54,7 @@ export interface IRawGalleryExtensionVersion {
readonly assetUri: string;
readonly fallbackAssetUri: string;
readonly files: IRawGalleryExtensionFile[];
readonly properties?: IRawGalleryExtensionProperty[];
properties?: IRawGalleryExtensionProperty[];
readonly targetPlatform?: string;
}
@@ -348,6 +348,11 @@ function getEngine(version: IRawGalleryExtensionVersion): string {
return (values.length > 0 && values[0].value) || '';
}
function setEngine(version: IRawGalleryExtensionVersion, engine: string): void {
version.properties = version.properties ?? [];
version.properties.push({ key: PropertyType.Engine, value: engine });
}
function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean {
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.PreRelease) : [];
return values.length > 0 && values[0].value === 'true';
@@ -442,6 +447,24 @@ export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], p
return versions;
}
/**
* Filters extension versions to return only the relevant versions for a given target platform.
*
* This function processes a list of extension versions (expected to be sorted by version descending)
* and returns a filtered list containing:
* 1. All versions that are NOT compatible with the target platform (for other platforms)
* 2. At most one compatible release version (the first/latest one encountered)
* 3. At most one compatible pre-release version (the first/latest one encountered)
*
* When a platform-specific version (exactly matching targetPlatform) is encountered with the same
* version number as a previously stored universal/undefined version, it replaces that version.
* This ensures platform-specific builds are preferred over universal builds for the same version.
*
* @param versions - Array of extension versions, expected to be sorted by version number descending
* @param targetPlatform - The target platform to filter for (e.g., LINUX_X64, WIN32_X64)
* @param allTargetPlatforms - All target platforms the extension supports
* @returns Filtered array of versions relevant for the target platform
*/
export function filterLatestExtensionVersionsForTargetPlatform(versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[]): IRawGalleryExtensionVersion[] {
const latestVersions: IRawGalleryExtensionVersion[] = [];
@@ -458,19 +481,19 @@ export function filterLatestExtensionVersionsForTargetPlatform(versions: IRawGal
}
// For compatible versions, only include the first (latest) of each type
// Prefer specific target platform matches over undefined/universal platforms
// Prefer specific target platform matches over undefined/universal platforms only when version numbers are the same
if (isPreReleaseVersion(version)) {
if (preReleaseVersionIndex === -1) {
preReleaseVersionIndex = latestVersions.length;
latestVersions.push(version);
} else if (versionTargetPlatform === targetPlatform) {
} else if (versionTargetPlatform === targetPlatform && latestVersions[preReleaseVersionIndex].version === version.version) {
latestVersions[preReleaseVersionIndex] = version;
}
} else {
if (releaseVersionIndex === -1) {
releaseVersionIndex = latestVersions.length;
latestVersions.push(version);
} else if (versionTargetPlatform === targetPlatform) {
} else if (versionTargetPlatform === targetPlatform && latestVersions[releaseVersionIndex].version === version.version) {
latestVersions[releaseVersionIndex] = version;
}
}
@@ -829,11 +852,24 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
return 'NOT_FOUND';
}
const targetPlatform = options.targetPlatform ?? CURRENT_TARGET_PLATFORM;
const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);
const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion(
const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersionFromLatestVersions(rawGalleryExtension, rawGalleryExtension.versions, extensionInfo, options, allTargetPlatforms);
if (!rawGalleryExtensionVersion) {
return 'NOT_COMPATIBLE';
}
return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService);
}
private async getValidRawGalleryExtensionVersionFromLatestVersions(rawGalleryExtension: IRawGalleryExtension, latestVersions: IRawGalleryExtensionVersion[], extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, allTargetPlatforms: TargetPlatform[]): Promise<IRawGalleryExtensionVersion | null> {
const targetPlatform = options.targetPlatform ?? CURRENT_TARGET_PLATFORM;
const latestExtensionVersionsForTargetPlatform = filterLatestExtensionVersionsForTargetPlatform(latestVersions, targetPlatform, allTargetPlatforms);
// First, find a valid version matching the requested type (pre-release or release)
const result = await this.getValidRawGalleryExtensionVersion(
rawGalleryExtension,
filterLatestExtensionVersionsForTargetPlatform(rawGalleryExtension.versions, targetPlatform, allTargetPlatforms),
latestExtensionVersionsForTargetPlatform,
{
targetPlatform,
compatible: !!options.compatible,
@@ -841,14 +877,63 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
version: this.productService.version,
date: this.productService.date
},
version: extensionInfo.preRelease ? VersionKind.Latest : VersionKind.Release
version: extensionInfo.preRelease ? VersionKind.Prerelease : VersionKind.Release
}, allTargetPlatforms);
if (rawGalleryExtensionVersion) {
return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService);
// For release version requests, simply return the found release version
if (!extensionInfo.preRelease) {
return result;
}
return 'NOT_COMPATIBLE';
// For pre-release version requests, we need to consider both pre-release and release versions
const prereleaseVersion = result;
const releaseVersion = await this.getValidRawGalleryExtensionVersion(
rawGalleryExtension,
latestExtensionVersionsForTargetPlatform,
{
targetPlatform,
compatible: !!options.compatible,
productVersion: options.productVersion ?? {
version: this.productService.version,
date: this.productService.date
},
version: VersionKind.Release
}, allTargetPlatforms);
// When both versions exist, return whichever has the higher version number
if (prereleaseVersion && releaseVersion) {
return semver.gt(releaseVersion.version, prereleaseVersion.version) ? releaseVersion : prereleaseVersion;
}
// Special handling for compatible version requests
if (options.compatible) {
// If we have a compatible release version, check if it's better than any pre-release
if (releaseVersion) {
// Check if there exists any pre-release version (ignoring compatibility)
const anyPrereleaseVersion = await this.getValidRawGalleryExtensionVersion(
rawGalleryExtension,
latestExtensionVersionsForTargetPlatform,
{
targetPlatform,
compatible: false,
productVersion: options.productVersion ?? {
version: this.productService.version,
date: this.productService.date
},
version: VersionKind.Prerelease
}, allTargetPlatforms);
// If no pre-release exists or the release version is greater, prefer the compatible release
// This ensures users get a stable compatible version when pre-releases aren't newer or compatible
if (!anyPrereleaseVersion || semver.gt(releaseVersion.version, anyPrereleaseVersion.version)) {
return releaseVersion;
}
}
return prereleaseVersion;
}
// Return pre-release if available, otherwise release, otherwise null
return prereleaseVersion ?? releaseVersion ?? null;
}
async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<IGalleryExtension | null> {
@@ -962,40 +1047,54 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
private async isEngineValid(extensionId: string, version: string, engine: string | undefined, manifestAsset: IGalleryExtensionAsset | null, productVersion: IProductVersion): Promise<boolean> {
if (!engine) {
if (!manifestAsset) {
this.logService.error(`Missing engine and manifest asset for the extension ${extensionId} with version ${version}`);
return false;
}
try {
type GalleryServiceEngineFallbackClassification = {
owner: 'sandy081';
comment: 'Fallback request when engine is not found in properties of an extension version';
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };
extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };
};
type GalleryServiceEngineFallbackEvent = {
extension: string;
extensionVersion: string;
};
this.telemetryService.publicLog2<GalleryServiceEngineFallbackEvent, GalleryServiceEngineFallbackClassification>('galleryService:engineFallback', { extension: extensionId, extensionVersion: version });
const headers = { 'Accept-Encoding': 'gzip' };
const context = await this.getAsset(extensionId, manifestAsset, AssetType.Manifest, version, { headers });
const manifest = await asJson<IExtensionManifest>(context);
if (!manifest) {
this.logService.error(`Manifest was not found for the extension ${extensionId} with version ${version}`);
return false;
}
engine = manifest.engines.vscode;
engine = await this.getEngine(extensionId, version, manifestAsset);
} catch (error) {
this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));
return false;
}
}
if (!engine) {
this.logService.error(`Missing engine for the extension ${extensionId} with version ${version}`);
return false;
}
return isEngineValid(engine, productVersion.version, productVersion.date);
}
private async getEngine(extensionId: string, version: string, manifestAsset: IGalleryExtensionAsset | null): Promise<string | undefined> {
if (!manifestAsset) {
this.logService.error(`Missing engine and manifest asset for the extension ${extensionId} with version ${version}`);
return undefined;
}
try {
type GalleryServiceEngineFallbackClassification = {
owner: 'sandy081';
comment: 'Fallback request when engine is not found in properties of an extension version';
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };
extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };
};
type GalleryServiceEngineFallbackEvent = {
extension: string;
extensionVersion: string;
};
this.telemetryService.publicLog2<GalleryServiceEngineFallbackEvent, GalleryServiceEngineFallbackClassification>('galleryService:engineFallback', { extension: extensionId, extensionVersion: version });
const headers = { 'Accept-Encoding': 'gzip' };
const context = await this.getAsset(extensionId, manifestAsset, AssetType.Manifest, version, { headers });
const manifest = await asJson<IExtensionManifest>(context);
if (!manifest) {
this.logService.error(`Manifest was not found for the extension ${extensionId} with version ${version}`);
return undefined;
}
return manifest.engines.vscode;
} catch (error) {
this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));
return undefined;
}
}
async query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>> {
const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();
@@ -1211,6 +1310,9 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
for (let index = 0; index < rawGalleryExtensionVersions.length; index++) {
const rawGalleryExtensionVersion = rawGalleryExtensionVersions[index];
if (criteria.compatible) {
await this.setEngineIfNotExists(extensionIdentifier.id, rawGalleryExtensionVersion);
}
if (await this.isValidVersion(
{
id: extensionIdentifier.id,
@@ -1243,6 +1345,21 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
return rawGalleryExtension.versions[0];
}
private async setEngineIfNotExists(extensionId: string, rawGalleryExtensionVersion: IRawGalleryExtensionVersion): Promise<void> {
if (getEngine(rawGalleryExtensionVersion)) {
return;
}
try {
const engine = await this.getEngine(extensionId, rawGalleryExtensionVersion.version, getVersionAsset(rawGalleryExtensionVersion, AssetType.Manifest));
if (engine) {
setEngine(rawGalleryExtensionVersion, engine);
}
} catch (error) {
this.logService.error(`Error while getting the engine for the version ${rawGalleryExtensionVersion.version}.`, getErrorMessage(error));
}
}
private async queryRawGalleryExtensions(query: Query, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IRawGalleryExtensionsResult> {
const extensionsQueryApi = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionQueryService);
@@ -14,6 +14,7 @@ import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, I
import { areSameExtensions, getExtensionId, getGalleryExtensionId, getIdAndVersion } from './extensionManagementUtil.js';
import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest } from '../../extensions/common/extensions.js';
import { ILogger } from '../../log/common/log.js';
import { IProductService } from '../../product/common/productService.js';
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
@@ -25,10 +26,14 @@ type InstallGalleryExtensionInfo = { id: string; version?: string; installOption
export class ExtensionManagementCLI {
constructor(
private readonly extensionsForceVersionByQuality: readonly string[],
protected readonly logger: ILogger,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
) { }
@IProductService private readonly productService: IProductService,
) {
this.extensionsForceVersionByQuality = this.extensionsForceVersionByQuality.map(e => e.toLowerCase());
}
protected get location(): string | undefined {
return undefined;
@@ -81,6 +86,9 @@ export class ExtensionManagementCLI {
const installVSIXInfos: InstallVSIXInfo[] = [];
const installExtensionInfos: InstallGalleryExtensionInfo[] = [];
const addInstallExtensionInfo = (id: string, version: string | undefined, isBuiltin: boolean) => {
if (this.extensionsForceVersionByQuality?.some(e => e === id.toLowerCase())) {
version = this.productService.quality !== 'stable' ? 'prerelease' : undefined;
}
installExtensionInfos.push({ id, version: version !== 'prerelease' ? version : undefined, installOptions: { ...installOptions, isBuiltin, installPreReleaseVersion: version === 'prerelease' || installOptions.installPreReleaseVersion } });
};
for (const extension of extensions) {
@@ -133,19 +133,32 @@ suite('Extension Gallery Service', () => {
assert.deepStrictEqual(result, versions);
});
test('should include both release and pre-release versions for same platform', () => {
const version1 = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64);
const version2 = aPreReleaseExtensionVersion('0.9.0', TargetPlatform.WIN32_X64); // Different version number
const versions = [version1, version2];
test('should include latest release and latest pre-release versions for same platform', () => {
const release = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64);
const prerelease = aPreReleaseExtensionVersion('0.9.0', TargetPlatform.WIN32_X64);
const versions = [release, prerelease];
const allTargetPlatforms = [TargetPlatform.WIN32_X64];
const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms);
// Should include both since they have different version numbers
assert.strictEqual(result.length, 2);
assert.strictEqual(result[0], version1);
assert.strictEqual(result[1], version2);
assert.strictEqual(result[0], release);
assert.strictEqual(result[1], prerelease);
});
test('should include latest prerelease and latest release versions for same platform', () => {
const prerelease = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.WIN32_X64);
const release = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64);
const versions = [prerelease, release];
const allTargetPlatforms = [TargetPlatform.WIN32_X64];
const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms);
// Should include both since they have different version numbers
assert.strictEqual(result.length, 2);
assert.strictEqual(result[0], prerelease);
assert.strictEqual(result[1], release);
});
test('should include one version per target platform for release versions', () => {
@@ -164,33 +177,6 @@ suite('Extension Gallery Service', () => {
assert.ok(result.includes(version3)); // Non-compatible, included
});
test('should separate release and pre-release versions', () => {
const releaseVersion = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64);
const preReleaseVersion = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.WIN32_X64);
const versions = [releaseVersion, preReleaseVersion];
const allTargetPlatforms = [TargetPlatform.WIN32_X64];
const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms);
// Should include both since they are different types (release vs pre-release)
assert.strictEqual(result.length, 2);
assert.ok(result.includes(releaseVersion));
assert.ok(result.includes(preReleaseVersion));
});
test('should include both release and pre-release versions for same platform with different version numbers', () => {
const preRelease1 = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.WIN32_X64);
const release2 = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); // Different version number
const versions = [preRelease1, release2];
const allTargetPlatforms = [TargetPlatform.WIN32_X64];
const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms);
// Should include both since they have different version numbers
assert.strictEqual(result.length, 2);
assert.strictEqual(result[0], preRelease1);
assert.strictEqual(result[1], release2);
});
test('should handle versions without target platform (UNDEFINED)', () => {
const version1 = aExtensionVersion('1.0.0'); // No target platform specified
@@ -281,20 +267,21 @@ suite('Extension Gallery Service', () => {
assert.ok(!result.includes(lowerVersionUniversal)); // Filtered (second compatible release)
});
test('should handle lower version with specific platform vs higher version with universal platform', () => {
// Reverse scenario: older version for specific platform vs newer version with universal compatibility
const lowerVersionSpecificPlatform = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64);
test('should handle higher version with universal platform vs lower version with specific platform', () => {
// Scenario: higher universal version comes first, then lower platform-specific version
const higherVersionUniversal = aExtensionVersion('2.0.0'); // UNDEFINED/universal platform
const lowerVersionSpecificPlatform = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64);
const versions = [lowerVersionSpecificPlatform, higherVersionUniversal];
const versions = [higherVersionUniversal, lowerVersionSpecificPlatform];
const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64];
const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms);
// Both are compatible with WIN32_X64, but only the first release version should be included
// Both are compatible with WIN32_X64, the first (higher) version should be kept
// Platform-specific version should NOT replace since it has a different (lower) version number
assert.strictEqual(result.length, 1);
assert.ok(result.includes(lowerVersionSpecificPlatform)); // First compatible release
assert.ok(!result.includes(higherVersionUniversal)); // Filtered (second compatible release)
assert.ok(result.includes(higherVersionUniversal)); // First compatible release (higher version)
assert.ok(!result.includes(lowerVersionSpecificPlatform)); // Filtered (lower version)
});
test('should handle multiple specific platforms vs universal platform with version differences', () => {
@@ -391,19 +378,20 @@ suite('Extension Gallery Service', () => {
assert.ok(!result.includes(universalVersion));
});
test('should handle both release and pre-release with replacement', () => {
// Both release and pre-release starting with undefined and then getting specific platform
const undefinedRelease = aExtensionVersion('1.0.0'); // UNDEFINED release
const specificRelease = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); // Specific release
test('should handle both release and pre-release with same version replacement', () => {
// Both release and pre-release with undefined platform, then specific platform with same versions
// Versions sorted by version descending (pre-release 1.1.0, release 1.0.0, then same versions with specific platform)
const undefinedPreRelease = aPreReleaseExtensionVersion('1.1.0'); // UNDEFINED pre-release
const specificPreRelease = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.WIN32_X64); // Specific pre-release
const specificPreRelease = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.WIN32_X64); // Specific pre-release (same version)
const undefinedRelease = aExtensionVersion('1.0.0'); // UNDEFINED release
const specificRelease = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); // Specific release (same version)
const versions = [undefinedRelease, undefinedPreRelease, specificRelease, specificPreRelease];
const versions = [undefinedPreRelease, specificPreRelease, undefinedRelease, specificRelease];
const allTargetPlatforms = [TargetPlatform.WIN32_X64];
const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms);
// Should return both specific platform versions
// Should return both specific platform versions (they replaced the undefined ones)
assert.strictEqual(result.length, 2);
assert.ok(result.includes(specificRelease));
assert.ok(result.includes(specificPreRelease));
@@ -427,21 +415,47 @@ suite('Extension Gallery Service', () => {
});
test('should handle replacement with non-compatible versions in between', () => {
// Versions sorted by version descending
const undefinedVersion = aExtensionVersion('1.0.0'); // UNDEFINED, compatible with WIN32_X64
const nonCompatibleVersion = aExtensionVersion('0.9.0', TargetPlatform.LINUX_ARM64); // Non-compatible platform
const specificVersion = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); // Specific for WIN32_X64
const specificVersion = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); // Specific for WIN32_X64 (same version)
const nonCompatibleVersion = aExtensionVersion('0.9.0', TargetPlatform.LINUX_ARM64); // Non-compatible platform (lower version)
const versions = [undefinedVersion, nonCompatibleVersion, specificVersion];
const versions = [undefinedVersion, specificVersion, nonCompatibleVersion];
const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64];
const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms);
// Should return specific WIN32_X64 version (replacing undefined) and non-compatible LINUX_ARM64 version
// Should return specific WIN32_X64 version (replacing undefined since same version) and non-compatible LINUX_ARM64 version
assert.strictEqual(result.length, 2);
assert.ok(result.includes(specificVersion));
assert.ok(result.includes(nonCompatibleVersion));
assert.ok(!result.includes(undefinedVersion));
});
test('should filter versions for linux-x64 target platform with mixed universal and platform-specific versions', () => {
// Data from real extension versions (sorted by version descending, as returned by gallery API):
// 0.15.0 - pre-release, universal
// 0.14.0 - release, universal
// 0.6.0 - release, linux-x64
// 0.5.1 - pre-release, linux-x64
const versions = [
aPreReleaseExtensionVersion('0.15.0'), // pre-release, universal (highest version)
aExtensionVersion('0.14.0'), // release, universal
aExtensionVersion('0.6.0', TargetPlatform.LINUX_X64), // release, linux-x64
aPreReleaseExtensionVersion('0.5.1', TargetPlatform.LINUX_X64), // pre-release, linux-x64 (lowest version)
];
const allTargetPlatforms = [TargetPlatform.LINUX_X64];
const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.LINUX_X64, allTargetPlatforms);
// Expected:
// - 0.15.0 universal (first compatible pre-release, higher version than 0.5.1 linux-x64)
// - 0.14.0 universal (first compatible release, higher version than 0.6.0 linux-x64)
// Platform-specific versions are NOT preferred when they have lower version numbers
assert.strictEqual(result.length, 2);
assert.ok(result.includes(versions[0])); // 0.15.0 universal (pre-release)
assert.ok(result.includes(versions[1])); // 0.14.0 universal (release)
});
});
});
@@ -235,6 +235,7 @@ export interface IExtensionContributions {
readonly chatPromptFiles?: ReadonlyArray<IChatFileContribution>;
readonly chatInstructions?: ReadonlyArray<IChatFileContribution>;
readonly chatAgents?: ReadonlyArray<IChatFileContribution>;
readonly chatSkills?: ReadonlyArray<IChatFileContribution>;
readonly languageModelTools?: ReadonlyArray<IToolContribution>;
readonly languageModelToolSets?: ReadonlyArray<IToolSetContribution>;
readonly mcpServerDefinitionProviders?: ReadonlyArray<IMcpCollectionContribution>;
@@ -73,7 +73,9 @@ export abstract class AbstractCommonMcpManagementService extends Disposable impl
// remote
if (packageType === RegistryType.REMOTE && manifest.remotes?.length) {
const { inputs, variables } = this.processKeyValueInputs(manifest.remotes[0].headers ?? []);
const url = manifest.remotes[0].url;
const headers = manifest.remotes[0].headers ?? [];
const { inputs, variables } = this.processKeyValueInputs(url.startsWith('https://api.githubcopilot.com/mcp') ? headers.filter(h => h.name.toLowerCase() !== 'authorization') : headers);
return {
mcpServerConfiguration: {
config: {
@@ -149,7 +151,7 @@ export abstract class AbstractCommonMcpManagementService extends Disposable impl
args.push(serverPackage.version ? `${serverPackage.identifier}@${serverPackage.version}` : serverPackage.identifier);
args.push('--yes'); // installation is confirmed by the UI, so --yes is appropriate here
if (serverPackage.registryBaseUrl) {
args.push('--add-source', serverPackage.registryBaseUrl);
args.push('--source', serverPackage.registryBaseUrl);
}
if (serverPackage.packageArguments?.length) {
args.push('--');
@@ -32,7 +32,11 @@ export class McpUserResourceManagementService extends CommonMcpUserResourceManag
try {
const manifest = await this.updateMetadataFromGallery(server);
const packageType = options?.packageType ?? manifest.packages?.[0]?.registryType ?? RegistryType.REMOTE;
const packageType = options?.packageType ?? (
manifest.remotes?.length
? RegistryType.REMOTE
: (manifest.packages?.[0]?.registryType ?? RegistryType.REMOTE)
);
const { mcpServerConfiguration, notices } = this.getMcpServerConfigurationFromManifest(manifest, packageType);
@@ -548,7 +548,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => {
assert.deepStrictEqual(result.mcpServerConfiguration.config.args, [
'Company.Internal.McpServer@4.5.6',
'--yes',
'--add-source', 'https://nuget.company.com/v3/index.json'
'--source', 'https://nuget.company.com/v3/index.json'
]);
}
});
+2 -2
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IStringDictionary } from '../../../base/common/collections.js';
import { IDefaultAccount } from '../../../base/common/defaultAccount.js';
import { IPolicyData } from '../../../base/common/defaultAccount.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { Disposable } from '../../../base/common/lifecycle.js';
@@ -14,7 +14,7 @@ import { createDecorator } from '../../instantiation/common/instantiation.js';
export type PolicyValue = string | number | boolean;
export type PolicyDefinition = {
type: 'string' | 'number' | 'boolean';
value?: (account: IDefaultAccount) => string | number | boolean | undefined;
value?: (policyData: IPolicyData) => string | number | boolean | undefined;
};
export const IPolicyService = createDecorator<IPolicyService>('policy');
+51 -2
View File
@@ -6,7 +6,7 @@
import type * as http from 'http';
import type * as https from 'https';
import { parse as parseUrl } from 'url';
import { Promises } from '../../../base/common/async.js';
import { Promises, timeout } from '../../../base/common/async.js';
import { streamToBufferReadableStream } from '../../../base/common/buffer.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { CancellationError, getErrorMessage } from '../../../base/common/errors.js';
@@ -21,6 +21,26 @@ import { AbstractRequestService, AuthInfo, Credentials, IRequestService, systemC
import { Agent, getProxyAgent } from './proxy.js';
import { createGunzip } from 'zlib';
const TRANSIENT_ERROR_CODES = new Set([
'EAI_AGAIN', // DNS lookup timed out
'ECONNREFUSED', // Connection refused by server
'EHOSTDOWN', // Host is down
'EHOSTUNREACH', // No route to host
'ENETDOWN', // Network is down
'ENETUNREACH', // Network is unreachable
'EPROTO' // Protocol error (TLS/SSL handshake failure)
]);
const IDEMPOTENT_HTTP_METHODS_REGEX = /^(GET|HEAD|OPTIONS)$/i;
function isTransientError(error: unknown): boolean {
if (error instanceof Error) {
const code = (error as NodeJS.ErrnoException).code;
return !!code && TRANSIENT_ERROR_CODES.has(code);
}
return false;
}
export interface IRawRequestFunction {
(options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
}
@@ -153,6 +173,31 @@ async function getNodeRequest(options: IRequestOptions): Promise<IRawRequestFunc
}
export async function nodeRequest(options: NodeRequestOptions, token: CancellationToken): Promise<IRequestContext> {
const maxRetries = 3;
let lastError: Error | undefined;
const isIdempotent = IDEMPOTENT_HTTP_METHODS_REGEX.test(options.type || 'GET');
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await nodeRequestAttempt(options, token);
} catch (error) {
lastError = error as Error;
if (error instanceof CancellationError) {
throw error;
}
if (!isIdempotent || !isTransientError(error) || attempt === maxRetries) {
throw error;
}
await timeout(100 * attempt, token);
}
}
throw lastError;
}
async function nodeRequestAttempt(options: NodeRequestOptions, token: CancellationToken): Promise<IRequestContext> {
return Promises.withAsyncBody<IRequestContext>(async (resolve, reject) => {
const endpoint = parseUrl(options.url!);
const rawRequest = options.getRawRequest
@@ -238,10 +283,14 @@ export async function nodeRequest(options: NodeRequestOptions, token: Cancellati
req.end();
token.onCancellationRequested(() => {
const cancellationListener = token.onCancellationRequested(() => {
cancellationListener.dispose();
req.abort();
reject(new CancellationError());
});
req.on('response', () => cancellationListener.dispose());
req.on('error', () => cancellationListener.dispose());
});
}
@@ -6,9 +6,10 @@
import assert from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
import { NullLogService } from '../../../log/common/log.js';
import { lookupKerberosAuthorization } from '../../node/requestService.js';
import { IRawRequestFunction, lookupKerberosAuthorization, nodeRequest } from '../../node/requestService.js';
import { isWindows } from '../../../../base/common/platform.js';
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { CancellationError } from '../../../../base/common/errors.js';
suite('Request Service', () => {
const store = ensureNoDisposablesAreLeakedInTestSuite();
@@ -28,4 +29,263 @@ suite('Request Service', () => {
, `Unexpected error: ${err}`);
}
});
test('Request cancellation during retry backoff', async () => {
const cts = store.add(new CancellationTokenSource());
const startTime = Date.now();
setTimeout(() => cts.cancel(), 50);
try {
await nodeRequest({ url: 'http://localhost:9999/nonexistent' }, cts.token);
assert.fail('Request should have been cancelled');
} catch (err) {
const elapsed = Date.now() - startTime;
assert.ok(err instanceof CancellationError, 'Error should be CancellationError');
assert.ok(elapsed < 200, `Request should be cancelled quickly, but took ${elapsed}ms`);
}
});
test('should retry GET requests on transient errors', async () => {
let attemptCount = 0;
const mockRawRequest = (_opts: any, callback: Function) => {
attemptCount++;
const currentAttempt = attemptCount;
const mockReq: any = {
on: (event: string, handler: Function) => {
if (event === 'error' && currentAttempt < 3) {
const err = new Error('Connection refused') as NodeJS.ErrnoException;
err.code = 'ECONNREFUSED';
setTimeout(() => handler(err), 0);
}
},
end: () => {
if (currentAttempt >= 3) {
// Succeed on third attempt by calling the response callback
setTimeout(() => callback({ statusCode: 200, headers: {}, on: () => { }, pipe: () => ({ on: () => { } }) }), 0);
}
},
abort: () => { },
setTimeout: () => { }
};
return mockReq;
};
try {
await nodeRequest({
url: 'http://example.com',
type: 'GET',
getRawRequest: () => mockRawRequest as IRawRequestFunction
}, CancellationToken.None);
} catch (err) {
// Expected to eventually succeed or fail after retries
}
assert.ok(attemptCount > 1, 'GET request should have been retried');
});
test('should NOT retry POST requests', async () => {
let attemptCount = 0;
const mockRawRequest = () => {
attemptCount++;
const mockReq: any = {
on: (event: string, handler: Function) => {
if (event === 'error') {
const err = new Error('Connection refused') as NodeJS.ErrnoException;
err.code = 'ECONNREFUSED';
setTimeout(() => handler(err), 0);
}
},
end: () => { },
abort: () => { },
setTimeout: () => { }
};
return mockReq;
};
try {
await nodeRequest({
url: 'http://example.com',
type: 'POST',
getRawRequest: () => mockRawRequest
}, CancellationToken.None);
assert.fail('Should have thrown an error');
} catch (err) {
assert.ok(err instanceof Error);
}
assert.strictEqual(attemptCount, 1, 'POST request should not have been retried');
});
test('should retry HEAD requests on transient errors', async () => {
let attemptCount = 0;
const mockRawRequest = (_opts: any, callback: Function) => {
attemptCount++;
const currentAttempt = attemptCount;
const mockReq: any = {
on: (event: string, handler: Function) => {
if (event === 'error' && currentAttempt < 3) {
const err = new Error('Host unreachable') as NodeJS.ErrnoException;
err.code = 'EHOSTUNREACH';
setTimeout(() => handler(err), 0);
}
},
end: () => {
if (currentAttempt >= 3) {
setTimeout(() => callback({ statusCode: 200, headers: {}, on: () => { }, pipe: () => ({ on: () => { } }) }), 0);
}
},
abort: () => { },
setTimeout: () => { }
};
return mockReq;
};
try {
await nodeRequest({
url: 'http://example.com',
type: 'HEAD',
getRawRequest: () => mockRawRequest as IRawRequestFunction
}, CancellationToken.None);
} catch (err) {
// Expected to eventually succeed or fail after retries
}
assert.ok(attemptCount > 1, 'HEAD request should have been retried');
});
test('should retry OPTIONS requests on transient errors', async () => {
let attemptCount = 0;
const mockRawRequest = (_opts: any, callback: Function) => {
attemptCount++;
const currentAttempt = attemptCount;
const mockReq: any = {
on: (event: string, handler: Function) => {
if (event === 'error' && currentAttempt < 3) {
const err = new Error('Network unreachable') as NodeJS.ErrnoException;
err.code = 'ENETUNREACH';
setTimeout(() => handler(err), 0);
}
},
end: () => {
if (currentAttempt >= 3) {
setTimeout(() => callback({ statusCode: 200, headers: {}, on: () => { }, pipe: () => ({ on: () => { } }) }), 0);
}
},
abort: () => { },
setTimeout: () => { }
};
return mockReq;
};
try {
await nodeRequest({
url: 'http://example.com',
type: 'OPTIONS',
getRawRequest: () => mockRawRequest as IRawRequestFunction
}, CancellationToken.None);
} catch (err) {
// Expected to eventually succeed or fail after retries
}
assert.ok(attemptCount > 1, 'OPTIONS request should have been retried');
});
test('should NOT retry DELETE requests', async () => {
let attemptCount = 0;
const mockRawRequest = () => {
attemptCount++;
const mockReq: any = {
on: (event: string, handler: Function) => {
if (event === 'error') {
const err = new Error('Connection refused') as NodeJS.ErrnoException;
err.code = 'ECONNREFUSED';
setTimeout(() => handler(err), 0);
}
},
end: () => { },
abort: () => { },
setTimeout: () => { }
};
return mockReq;
};
try {
await nodeRequest({
url: 'http://example.com',
type: 'DELETE',
getRawRequest: () => mockRawRequest
}, CancellationToken.None);
assert.fail('Should have thrown an error');
} catch (err) {
assert.ok(err instanceof Error);
}
assert.strictEqual(attemptCount, 1, 'DELETE request should not have been retried');
});
test('should NOT retry PUT requests', async () => {
let attemptCount = 0;
const mockRawRequest = () => {
attemptCount++;
const mockReq: any = {
on: (event: string, handler: Function) => {
if (event === 'error') {
const err = new Error('Connection refused') as NodeJS.ErrnoException;
err.code = 'ECONNREFUSED';
setTimeout(() => handler(err), 0);
}
},
end: () => { },
abort: () => { },
setTimeout: () => { }
};
return mockReq;
};
try {
await nodeRequest({
url: 'http://example.com',
type: 'PUT',
getRawRequest: () => mockRawRequest
}, CancellationToken.None);
assert.fail('Should have thrown an error');
} catch (err) {
assert.ok(err instanceof Error);
}
assert.strictEqual(attemptCount, 1, 'PUT request should not have been retried');
});
test('should NOT retry PATCH requests', async () => {
let attemptCount = 0;
const mockRawRequest = () => {
attemptCount++;
const mockReq: any = {
on: (event: string, handler: Function) => {
if (event === 'error') {
const err = new Error('Connection refused') as NodeJS.ErrnoException;
err.code = 'ECONNREFUSED';
setTimeout(() => handler(err), 0);
}
},
end: () => { },
abort: () => { },
setTimeout: () => { }
};
return mockReq;
};
try {
await nodeRequest({
url: 'http://example.com',
type: 'PATCH',
getRawRequest: () => mockRawRequest
}, CancellationToken.None);
assert.fail('Should have thrown an error');
} catch (err) {
assert.ok(err instanceof Error);
}
assert.strictEqual(attemptCount, 1, 'PATCH request should not have been retried');
});
});
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IButtonStyles } from '../../../base/browser/ui/button/button.js';
import { IKeybindingLabelStyles } from '../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropOverBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listDropBetweenBackground, radioActiveBackground, radioActiveForeground, radioInactiveBackground, radioInactiveForeground, radioInactiveBorder, radioInactiveHoverBackground, radioActiveBorder, checkboxDisabledBackground, checkboxDisabledForeground, widgetBorder } from '../common/colorRegistry.js';
import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonSecondaryBorder, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropOverBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listDropBetweenBackground, radioActiveBackground, radioActiveForeground, radioInactiveBackground, radioInactiveForeground, radioInactiveBorder, radioInactiveHoverBackground, radioActiveBorder, checkboxDisabledBackground, checkboxDisabledForeground, widgetBorder } from '../common/colorRegistry.js';
import { IProgressBarStyles } from '../../../base/browser/ui/progressbar/progressbar.js';
import { ICheckboxStyles, IToggleStyles } from '../../../base/browser/ui/toggle/toggle.js';
import { IDialogStyles } from '../../../base/browser/ui/dialog/dialog.js';
@@ -51,6 +51,7 @@ export const defaultButtonStyles: IButtonStyles = {
buttonSecondaryForeground: asCssVariable(buttonSecondaryForeground),
buttonSecondaryBackground: asCssVariable(buttonSecondaryBackground),
buttonSecondaryHoverBackground: asCssVariable(buttonSecondaryHoverBackground),
buttonSecondaryBorder: asCssVariable(buttonSecondaryBorder),
buttonBorder: asCssVariable(buttonBorder),
};
@@ -12,6 +12,7 @@ import { registerColor, transparent, lighten, darken, ColorTransformType } from
// Import the colors we need
import { foreground, contrastBorder, focusBorder, iconForeground } from './baseColors.js';
import { editorWidgetBackground } from './editorColors.js';
import { listHoverBackground } from './listColors.js';
// ----- input
@@ -130,15 +131,19 @@ export const buttonBorder = registerColor('button.border',
nls.localize('buttonBorder', "Button border color."));
export const buttonSecondaryForeground = registerColor('button.secondaryForeground',
{ dark: Color.white, light: Color.white, hcDark: Color.white, hcLight: foreground },
{ dark: foreground, light: foreground, hcDark: Color.white, hcLight: foreground },
nls.localize('buttonSecondaryForeground', "Secondary button foreground color."));
export const buttonSecondaryBackground = registerColor('button.secondaryBackground',
{ dark: '#3A3D41', light: '#5F6A79', hcDark: null, hcLight: Color.white },
{ dark: null, light: null, hcDark: null, hcLight: Color.white },
nls.localize('buttonSecondaryBackground', "Secondary button background color."));
export const buttonSecondaryBorder = registerColor('button.secondaryBorder',
transparent(buttonSecondaryForeground, 0.2),
nls.localize('buttonSecondaryBorder', "Secondary button border color."));
export const buttonSecondaryHoverBackground = registerColor('button.secondaryHoverBackground',
{ dark: lighten(buttonSecondaryBackground, 0.2), light: darken(buttonSecondaryBackground, 0.2), hcDark: null, hcLight: null },
{ dark: listHoverBackground, light: listHoverBackground, hcDark: null, hcLight: null },
nls.localize('buttonSecondaryHoverBackground', "Secondary button background color when hovering."));
// ------ radio
@@ -71,6 +71,7 @@ class CliMain extends Disposable {
await instantiationService.invokeFunction(async accessor => {
const configurationService = accessor.get(IConfigurationService);
const logService = accessor.get(ILogService);
const productService = accessor.get(IProductService);
// On Windows, configure the UNC allow list based on settings
if (isWindows) {
@@ -82,7 +83,7 @@ class CliMain extends Disposable {
}
try {
await this.doRun(instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(logService.getLevel(), false)));
await this.doRun(instantiationService.createInstance(ExtensionManagementCLI, productService.extensionsForceVersionByQuality ?? [], new ConsoleLogger(logService.getLevel(), false)));
} catch (error) {
logService.error(error);
console.error(getErrorMessage(error));
+1 -1
View File
@@ -243,7 +243,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyHostService, productService, extensionManagementService, configurationService));
const remoteExtensionsScanner = new RemoteExtensionsScannerService(instantiationService.createInstance(ExtensionManagementCLI, logService), environmentService, userDataProfilesService, extensionsScannerService, logService, extensionGalleryService, languagePackService, extensionManagementService);
const remoteExtensionsScanner = new RemoteExtensionsScannerService(instantiationService.createInstance(ExtensionManagementCLI, productService.extensionsForceVersionByQuality ?? [], logService), environmentService, userDataProfilesService, extensionsScannerService, logService, extensionGalleryService, languagePackService, extensionManagementService);
socketServer.registerChannel(RemoteExtensionsScannerChannelName, new RemoteExtensionsScannerChannel(remoteExtensionsScanner, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)));
socketServer.registerChannel(NativeMcpDiscoveryHelperChannelName, instantiationService.createInstance(NativeMcpDiscoveryHelperChannel, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)));
@@ -18,6 +18,7 @@ import { ServiceCollection } from '../../../platform/instantiation/common/servic
import { ILabelService } from '../../../platform/label/common/label.js';
import { AbstractMessageLogger, ILogger, LogLevel } from '../../../platform/log/common/log.js';
import { IOpenerService } from '../../../platform/opener/common/opener.js';
import { IProductService } from '../../../platform/product/common/productService.js';
import { IOpenWindowOptions, IWindowOpenable } from '../../../platform/window/common/window.js';
import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js';
import { IExtensionManagementServerService } from '../../services/extensionManagement/common/extensionManagement.js';
@@ -106,8 +107,9 @@ class RemoteExtensionManagementCLI extends ExtensionManagementCLI {
@ILabelService labelService: ILabelService,
@IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService,
@IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService,
@IProductService productService: IProductService,
) {
super(logger, extensionManagementService, extensionGalleryService);
super([], logger, extensionManagementService, extensionGalleryService, productService);
const remoteAuthority = envService.remoteAuthority;
this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined;
@@ -159,7 +159,13 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
this._proxy.$onDidChangeSelection(sessionId, items.map(item => (item as TransferQuickPickItem).handle));
}));
store.add(quickPick.onDidTriggerItemButton((e) => {
this._proxy.$onDidTriggerItemButton(sessionId, (e.item as TransferQuickPickItem).handle, (e.button as TransferQuickInputButton).handle);
const transferButton = e.button as TransferQuickInputButton;
this._proxy.$onDidTriggerItemButton(
sessionId,
(e.item as TransferQuickPickItem).handle,
transferButton.handle,
transferButton.toggle?.checked
);
}));
}
@@ -20,6 +20,7 @@ import { DataTransferFileCache } from '../common/shared/dataTransferCache.js';
import * as typeConvert from '../common/extHostTypeConverters.js';
import { IMarkdownString } from '../../../base/common/htmlContent.js';
import { IViewsService } from '../../services/views/common/viewsService.js';
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry.js';
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape {
@@ -33,7 +34,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
@IViewsService private readonly viewsService: IViewsService,
@INotificationService private readonly notificationService: INotificationService,
@IExtensionService private readonly extensionService: IExtensionService,
@ILogService private readonly logService: ILogService
@ILogService private readonly logService: ILogService,
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
@@ -138,6 +140,26 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
this._dataProviders.deleteAndDispose(treeViewId);
}
$logResolveTreeNodeRetry(extensionId: string, retryCount: number, exhausted: boolean): void {
type TreeViewResolveRetryEvent = {
extensionId: string;
retryCount: number;
exhausted: boolean;
};
type TreeViewResolveRetryClassification = {
extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension identifier.' };
retryCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Number of retry attempts made.' };
exhausted: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether all retry attempts were exhausted.' };
owner: 'alexr00';
comment: 'Tracks tree view resolve retries due to concurrent refresh races.';
};
this.telemetryService.publicLog2<TreeViewResolveRetryEvent, TreeViewResolveRetryClassification>('treeView.resolveRetry', {
extensionId,
retryCount,
exhausted
});
}
private async reveal(treeView: ITreeView, dataProvider: TreeViewDataProvider, itemIn: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise<void> {
options = options ? options : { select: false, focus: false };
const select = isUndefinedOrNull(options.select) ? false : options.select;
@@ -70,6 +70,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose);
this._workspaceTrustManagementService.onDidChangeTrust(this._onDidGrantWorkspaceTrust, this, this._toDispose);
this._workspaceTrustManagementService.onDidChangeTrustedFolders(this._onDidChangeWorkspaceTrustedFolders, this, this._toDispose);
}
dispose(): void {
@@ -251,6 +252,12 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return this._workspaceTrustRequestService.requestWorkspaceTrust(options);
}
async $isResourceTrusted(resource: UriComponents): Promise<boolean> {
const uri = URI.revive(resource);
const trustInfo = await this._workspaceTrustManagementService.getUriTrustInfo(uri);
return trustInfo.trusted;
}
private isWorkspaceTrusted(): boolean {
return this._workspaceTrustManagementService.isWorkspaceTrusted();
}
@@ -259,6 +266,10 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
this._proxy.$onDidGrantWorkspaceTrust();
}
private _onDidChangeWorkspaceTrustedFolders(): void {
this._proxy.$onDidChangeWorkspaceTrustedFolders();
}
// --- edit sessions ---
private registeredEditSessionProviders = new Map<number, IDisposable>();
@@ -1271,6 +1271,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'workspaceTrust');
return extHostWorkspace.requestWorkspaceTrust(options);
},
isResourceTrusted: (resource: vscode.Uri) => {
checkProposedApiEnabled(extension, 'workspaceTrust');
return extHostWorkspace.isResourceTrusted(resource);
},
onDidChangeWorkspaceTrustedFolders: (listener, thisArgs?, disposables?) => {
checkProposedApiEnabled(extension, 'workspaceTrust');
return _asExtensionEvent(extHostWorkspace.onDidChangeWorkspaceTrustedFolders)(listener, thisArgs, disposables);
},
onDidGrantWorkspaceTrust: (listener, thisArgs?, disposables?) => {
return _asExtensionEvent(extHostWorkspace.onDidGrantWorkspaceTrust)(listener, thisArgs, disposables);
},
@@ -354,6 +354,7 @@ export interface MainThreadTreeViewsShape extends IDisposable {
$setBadge(treeViewId: string, badge: IViewBadge | undefined): void;
$resolveDropFileData(destinationViewId: string, requestId: number, dataItemId: string): Promise<VSBuffer>;
$disposeTree(treeViewId: string): Promise<void>;
$logResolveTreeNodeRetry(extensionId: string, retryCount: number, exhausted: boolean): void;
}
export interface MainThreadDownloadServiceShape extends IDisposable {
@@ -1613,6 +1614,7 @@ export interface MainThreadWorkspaceShape extends IDisposable {
$loadCertificates(): Promise<string[]>;
$requestResourceTrust(options: ResourceTrustRequestOptionsDto): Promise<boolean | undefined>;
$requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined>;
$isResourceTrusted(resource: UriComponents): Promise<boolean>;
$registerEditSessionIdentityProvider(handle: number, scheme: string): void;
$unregisterEditSessionIdentityProvider(handle: number): void;
$registerCanonicalUriProvider(handle: number, scheme: string): void;
@@ -2096,6 +2098,7 @@ export interface ExtHostWorkspaceShape {
$acceptWorkspaceData(workspace: IWorkspaceData | null): void;
$handleTextSearchResult(result: search.IRawFileMatch2, requestId: number): void;
$onDidGrantWorkspaceTrust(): void;
$onDidChangeWorkspaceTrustedFolders(): void;
$getEditSessionIdentifier(folder: UriComponents, token: CancellationToken): Promise<string | undefined>;
$provideEditSessionIdentityMatch(folder: UriComponents, identity1: string, identity2: string, token: CancellationToken): Promise<EditSessionIdentityMatch | undefined>;
$onWillCreateEditSessionIdentity(folder: UriComponents, token: CancellationToken, timeout: number): Promise<void>;
@@ -2576,7 +2579,7 @@ export interface ExtHostQuickOpenShape {
$onDidAccept(sessionId: number): void;
$onDidChangeValue(sessionId: number, value: string): void;
$onDidTriggerButton(sessionId: number, handle: number, checked?: boolean): void;
$onDidTriggerItemButton(sessionId: number, itemHandle: number, buttonHandle: number): void;
$onDidTriggerItemButton(sessionId: number, itemHandle: number, buttonHandle: number, checked?: boolean): void;
$onDidHide(sessionId: number): void;
}
@@ -5,7 +5,7 @@
import type * as vscode from 'vscode';
import { coalesce } from '../../../base/common/arrays.js';
import { DeferredPromise, timeout } from '../../../base/common/async.js';
import { DeferredPromise, raceCancellation, timeout } from '../../../base/common/async.js';
import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
import { toErrorMessage } from '../../../base/common/errorMessage.js';
import { Emitter } from '../../../base/common/event.js';
@@ -51,7 +51,8 @@ export class ChatAgentResponseStream {
private readonly _proxy: IChatAgentProgressShape,
private readonly _commandsConverter: CommandsConverter,
private readonly _sessionDisposables: DisposableStore,
private readonly _pendingCarouselResolvers: Map</* requestId */string, Map</* resolveId */ string, DeferredPromise<Record<string, unknown> | undefined>>>
private readonly _pendingCarouselResolvers: Map</* requestId */string, Map</* resolveId */ string, DeferredPromise<Record<string, unknown> | undefined>>>,
private readonly _token: CancellationToken
) { }
close() {
@@ -330,8 +331,8 @@ export class ChatAgentResponseStream {
_report(dto);
// Wait for the user to submit answers
return deferred.p;
// Wait for the user to submit answers, but respect cancellation
return raceCancellation(deferred.p, that._token);
},
beginToolInvocation(toolCallId, toolName, streamData) {
throwIfDone(this.beginToolInvocation);
@@ -688,7 +689,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
this._sessionDisposables.set(request.sessionResource, sessionDisposables);
}
stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._commands.converter, sessionDisposables, this._pendingCarouselResolvers);
stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._commands.converter, sessionDisposables, this._pendingCarouselResolvers, token);
const model = await this.getModelForRequest(request, agent.extension);
const tools = await this.getToolsForRequest(agent.extension, request.userSelectedTools, model.id, token);
@@ -233,7 +233,7 @@ class ExtHostChatSession {
public readonly commandsConverter: CommandsConverter,
public readonly sessionDisposables: DisposableStore
) {
this._stream = new ChatAgentResponseStream(extension, request, proxy, commandsConverter, sessionDisposables, this._pendingCarouselResolvers);
this._stream = new ChatAgentResponseStream(extension, request, proxy, commandsConverter, sessionDisposables, this._pendingCarouselResolvers, CancellationToken.None);
}
get activeResponseStream() {
@@ -241,7 +241,7 @@ class ExtHostChatSession {
}
getActiveRequestStream(request: IChatAgentRequest) {
return new ChatAgentResponseStream(this.extension, request, this.proxy, this.commandsConverter, this.sessionDisposables, this._pendingCarouselResolvers);
return new ChatAgentResponseStream(this.extension, request, this.proxy, this.commandsConverter, this.sessionDisposables, this._pendingCarouselResolvers, CancellationToken.None);
}
}
@@ -252,10 +252,10 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
session?._fireDidTriggerButton(handle, checked);
}
$onDidTriggerItemButton(sessionId: number, itemHandle: number, buttonHandle: number): void {
$onDidTriggerItemButton(sessionId: number, itemHandle: number, buttonHandle: number, checked?: boolean): void {
const session = this._sessions.get(sessionId);
if (session instanceof ExtHostQuickPick) {
session._fireDidTriggerItemButton(itemHandle, buttonHandle);
session._fireDidTriggerItemButton(itemHandle, buttonHandle, checked);
}
}
@@ -568,7 +568,11 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
return {
iconPathDto: IconPath.from(button.iconPath),
tooltip: button.tooltip,
handle: i
handle: i,
toggle:
typeof button.toggle === 'object' && typeof button.toggle.checked === 'boolean'
? { checked: button.toggle.checked }
: undefined,
};
}),
});
@@ -670,13 +674,16 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
onDidTriggerItemButton = this._onDidTriggerItemButtonEmitter.event;
_fireDidTriggerItemButton(itemHandle: number, buttonHandle: number) {
_fireDidTriggerItemButton(itemHandle: number, buttonHandle: number, checked?: boolean) {
const item = this._handlesToItems.get(itemHandle)!;
if (!item || !item.buttons || !item.buttons.length) {
return;
}
const button = item.buttons[buttonHandle];
if (button) {
if (checked !== undefined && button.toggle) {
button.toggle.checked = checked;
}
this._onDidTriggerItemButtonEmitter.fire({
button,
item
+32 -15
View File
@@ -699,24 +699,41 @@ class ExtHostTreeView<T> extends Disposable {
return asPromise(() => this._dataProvider.getParent!(element));
}
private _resolveTreeNode(element: T, parent?: TreeNode): Promise<TreeNode> {
private async _resolveTreeNode(element: T, parent?: TreeNode): Promise<TreeNode> {
const node = this._nodes.get(element);
if (node) {
return Promise.resolve(node);
return node;
}
return asPromise(() => this._dataProvider.getTreeItem(element))
.then(extTreeItem => this._createHandle(element, extTreeItem, parent, true))
.then(handle => this.getChildren(parent ? parent.item.handle : undefined)
.then(() => {
const cachedElement = this.getExtensionElement(handle);
if (cachedElement) {
const node = this._nodes.get(cachedElement);
if (node) {
return Promise.resolve(node);
}
}
throw new Error(`Cannot resolve tree item for element ${handle} from extension ${this._extension.identifier.value}`);
}));
const extTreeItem = await asPromise(() => this._dataProvider.getTreeItem(element));
const handle = this._createHandle(element, extTreeItem, parent, true);
const children = await this.getChildren(parent ? parent.item.handle : undefined);
// If getChildren returned undefined, it means a concurrent refresh invalidated
// the fetch. Wait for the refresh to complete and check if the element was resolved.
if (children === undefined) {
this._logService.warn(`[${this._viewId}] Concurrent refresh detected in _resolveTreeNode for element ${handle} from extension ${this._extension.identifier.value}, waiting for refresh to complete`);
this._proxy.$logResolveTreeNodeRetry(this._extension.identifier.value, 1, false);
// Wait for any pending refresh to complete
await this._refreshPromise;
// Check if the element is now in the cache after the refresh completed
const cachedElement = this.getExtensionElement(handle);
if (cachedElement) {
const node = this._nodes.get(cachedElement);
if (node) {
return node;
}
}
// Still not found after refresh completed - log and throw
this._proxy.$logResolveTreeNodeRetry(this._extension.identifier.value, 1, true);
throw new Error(`Cannot resolve tree item for element ${handle} from extension ${this._extension.identifier.value}`);
}
const cachedElement = this.getExtensionElement(handle);
if (cachedElement) {
const node = this._nodes.get(cachedElement);
if (node) {
return node;
}
}
throw new Error(`Cannot resolve tree item for element ${handle} from extension ${this._extension.identifier.value}`);
}
private _getChildrenNodes(parentNodeOrHandle: TreeNode | TreeItemHandle | Root): TreeNode[] | undefined {
@@ -3717,8 +3717,8 @@ export namespace LanguageModelToolSource {
}
export namespace LanguageModelToolResult {
export function to(result: IToolResult): vscode.LanguageModelToolResult {
return new types.LanguageModelToolResult(result.content.map(item => {
export function to(result: IToolResult): vscode.ExtendedLanguageModelToolResult {
const toolResult = new types.LanguageModelToolResult(result.content.map(item => {
if (item.kind === 'text') {
return new types.LanguageModelTextPart(item.value, item.audience);
} else if (item.kind === 'data') {
@@ -3726,7 +3726,11 @@ export namespace LanguageModelToolResult {
} else {
return new types.LanguageModelPromptTsxPart(item.value);
}
}));
})) as vscode.ExtendedLanguageModelToolResult;
if (result.toolMetadata !== undefined) {
toolResult.toolMetadata = result.toolMetadata;
}
return toolResult;
}
export function from(result: vscode.ExtendedLanguageModelToolResult2, extension: IExtensionDescription): Dto<IToolResult> | SerializableObjectWithBuffers<Dto<IToolResult>> {
@@ -189,6 +189,9 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
private readonly _onDidGrantWorkspaceTrust = new Emitter<void>();
readonly onDidGrantWorkspaceTrust: Event<void> = this._onDidGrantWorkspaceTrust.event;
private readonly _onDidChangeWorkspaceTrustedFolders = new Emitter<void>();
readonly onDidChangeWorkspaceTrustedFolders: Event<void> = this._onDidChangeWorkspaceTrustedFolders.event;
private readonly _logService: ILogService;
private readonly _requestIdProvider: Counter;
private readonly _barrier: Barrier;
@@ -821,6 +824,14 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
}
}
$onDidChangeWorkspaceTrustedFolders(): void {
this._onDidChangeWorkspaceTrustedFolders.fire();
}
isResourceTrusted(resource: vscode.Uri): Promise<boolean> {
return this._proxy.$isResourceTrusted(resource);
}
// --- edit sessions ---
private _providerHandlePool = 0;
@@ -12,6 +12,7 @@ import { TestInstantiationService } from '../../../../platform/instantiation/tes
import { NullLogService } from '../../../../platform/log/common/log.js';
import { TestNotificationService } from '../../../../platform/notification/test/common/testNotificationService.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { NullTelemetryService } from '../../../../platform/telemetry/common/telemetryUtils.js';
import { MainThreadTreeViews } from '../../browser/mainThreadTreeViews.js';
import { ExtHostTreeViewsShape } from '../../common/extHost.protocol.js';
import { CustomTreeView } from '../../../browser/parts/views/treeView.js';
@@ -80,7 +81,7 @@ suite('MainThreadHostTreeView', function () {
return extHostTreeViewsShape;
}
drain(): any { return null; }
}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService()));
}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService(), NullTelemetryService));
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dropMimeTypes: [], dragMimeTypes: [], hasHandleDrag: false, hasHandleDrop: false, manuallyManageCheckboxes: false });
await testExtensionService.whenInstalledExtensionsRegistered();
});
+5 -16
View File
@@ -117,6 +117,7 @@ interface IInitialEditorsState {
const COMMAND_CENTER_SETTINGS = [
'chat.agentsControl.enabled',
'chat.unifiedAgentsBar.enabled',
'workbench.navigationControl.enabled',
'workbench.experimental.share.enabled',
];
@@ -354,12 +355,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
};
// Maybe maximize auxiliary bar when no editors, sidebar hidden, and panel hidden
// Maybe maximize auxiliary bar when no editors are visible
const maybeMaximizeAuxiliaryBar = () => {
if (
this.mainPartEditorService.visibleEditors.length === 0 &&
!this.isVisible(Parts.SIDEBAR_PART) &&
!this.isVisible(Parts.PANEL_PART) &&
this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_FORCE_MAXIMIZED) === true
) {
this.setAuxiliaryBarMaximized(true);
@@ -383,13 +382,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}));
this._register(this.editorGroupService.mainPart.onDidActivateGroup(showEditorIfHidden));
// Maybe maximize auxiliary bar when sidebar or panel hides
this._register(this.onDidChangePartVisibility(({ partId, visible }) => {
if (!visible && (partId === Parts.SIDEBAR_PART || partId === Parts.PANEL_PART)) {
maybeMaximizeAuxiliaryBar();
}
}));
// Revalidate center layout when active editor changes: diff editor quits centered mode
this._register(this.mainPartEditorService.onDidActiveEditorChange(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED))));
});
@@ -405,10 +397,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
].some(setting => e.affectsConfiguration(setting))) {
// Show Command Center if command center actions enabled
const shareEnabled = e.affectsConfiguration('workbench.experimental.share.enabled') && this.configurationService.getValue<boolean>('workbench.experimental.share.enabled');
const navigationControlEnabled = e.affectsConfiguration('workbench.navigationControl.enabled') && this.configurationService.getValue<boolean>('workbench.navigationControl.enabled');
const enabledCommandCenterAction = COMMAND_CENTER_SETTINGS.some(setting => e.affectsConfiguration(setting) && this.configurationService.getValue<boolean>(setting) === true);
if (shareEnabled || navigationControlEnabled) {
if (enabledCommandCenterAction) {
if (this.configurationService.getValue<boolean>(LayoutSettings.COMMAND_CENTER) === false) {
this.configurationService.updateValue(LayoutSettings.COMMAND_CENTER, true);
return; // onDidChangeConfiguration will be triggered again
@@ -2990,11 +2981,9 @@ class LayoutStateModel extends Disposable {
private applyOverrides(configuration: ILayoutStateLoadConfiguration): void {
// Auxiliary bar: Maximized settings
const auxiliaryBarForceMaximized = this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_FORCE_MAXIMIZED);
if (this.isNew[StorageScope.WORKSPACE] || auxiliaryBarForceMaximized) {
if (this.isNew[StorageScope.WORKSPACE]) {
const defaultAuxiliaryBarVisibility = this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_DEFAULT_VISIBILITY);
if (
auxiliaryBarForceMaximized ||
defaultAuxiliaryBarVisibility === 'maximized' ||
(defaultAuxiliaryBarVisibility === 'maximizedInWorkspace' && this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY)
) {
@@ -547,7 +547,7 @@ export class BreadcrumbsControl {
const pickerArrowSize = 8;
let pickerArrowOffset: number;
const data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement);
const data = dom.getDomNodePagePosition(event.node);
const y = data.top + data.height + pickerArrowSize;
if (y + maxHeight >= window.innerHeight) {
maxHeight = window.innerHeight - y - 30 /* room for shadow and status bar*/;
@@ -821,7 +821,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart {
private get activityActionsEnabled(): boolean {
const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);
return !this.isCompact && !this.isAuxiliary && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM || activityBarPosition === ActivityBarPosition.HIDDEN);
return !this.isCompact && !this.isAuxiliary && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM);
}
private get globalActionsEnabled(): boolean {
@@ -589,7 +589,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'type': 'boolean',
'default': false,
tags: ['experimental'],
'description': localize('secondarySideBarForceMaximized', "Controls whether the secondary side bar is enforced to always show maximized unless other parts or editors are showing."),
'description': localize('secondarySideBarForceMaximized', "Controls whether the secondary side bar is enforced to always show maximized on startup and when there are no open editors, in layouts that support a maximized secondary side bar."),
},
'workbench.secondarySideBar.showLabels': {
'type': 'boolean',
@@ -22,7 +22,8 @@ import {
BrowserViewStorageScope,
IBrowserViewCaptureScreenshotOptions,
IBrowserViewFindInPageOptions,
IBrowserViewFindInPageResult
IBrowserViewFindInPageResult,
IBrowserViewVisibilityEvent
} from '../../../../platform/browserView/common/browserView.js';
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
@@ -82,6 +83,7 @@ export interface IBrowserViewModel extends IDisposable {
readonly screenshot: VSBuffer | undefined;
readonly loading: boolean;
readonly focused: boolean;
readonly visible: boolean;
readonly canGoBack: boolean;
readonly isDevToolsOpen: boolean;
readonly canGoForward: boolean;
@@ -98,6 +100,7 @@ export interface IBrowserViewModel extends IDisposable {
readonly onDidChangeFavicon: Event<IBrowserViewFaviconChangeEvent>;
readonly onDidRequestNewPage: Event<IBrowserViewNewPageRequest>;
readonly onDidFindInPage: Event<IBrowserViewFindInPageResult>;
readonly onDidChangeVisibility: Event<IBrowserViewVisibilityEvent>;
readonly onDidClose: Event<void>;
readonly onWillDispose: Event<void>;
@@ -125,6 +128,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
private _screenshot: VSBuffer | undefined = undefined;
private _loading: boolean = false;
private _focused: boolean = false;
private _visible: boolean = false;
private _isDevToolsOpen: boolean = false;
private _canGoBack: boolean = false;
private _canGoForward: boolean = false;
@@ -150,6 +154,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
get favicon(): string | undefined { return this._favicon; }
get loading(): boolean { return this._loading; }
get focused(): boolean { return this._focused; }
get visible(): boolean { return this._visible; }
get isDevToolsOpen(): boolean { return this._isDevToolsOpen; }
get canGoBack(): boolean { return this._canGoBack; }
get canGoForward(): boolean { return this._canGoForward; }
@@ -193,6 +198,10 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
return this.browserViewService.onDynamicDidFindInPage(this.id);
}
get onDidChangeVisibility(): Event<IBrowserViewVisibilityEvent> {
return this.browserViewService.onDynamicDidChangeVisibility(this.id);
}
get onDidClose(): Event<void> {
return this.browserViewService.onDynamicDidClose(this.id);
}
@@ -221,6 +230,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
this._title = state.title;
this._loading = state.loading;
this._focused = state.focused;
this._visible = state.visible;
this._isDevToolsOpen = state.isDevToolsOpen;
this._canGoBack = state.canGoBack;
this._canGoForward = state.canGoForward;
@@ -262,6 +272,10 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
this._register(this.onDidChangeFocus(({ focused }) => {
this._focused = focused;
}));
this._register(this.onDidChangeVisibility(({ visible }) => {
this._visible = visible;
}));
}
async layout(bounds: IBrowserViewBounds): Promise<void> {
@@ -269,6 +283,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
}
async setVisible(visible: boolean): Promise<void> {
this._visible = visible; // Set optimistically so model is in sync immediately
return this.browserViewService.setVisible(this.id, visible);
}
@@ -5,7 +5,7 @@
import './media/browser.css';
import { localize } from '../../../../nls.js';
import { $, addDisposableListener, Dimension, disposableWindowInterval, EventType, IDomPosition, registerExternalFocusChecker } from '../../../../base/browser/dom.js';
import { $, addDisposableListener, Dimension, EventType, IDomPosition, registerExternalFocusChecker } from '../../../../base/browser/dom.js';
import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { RawContextKey, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
@@ -192,6 +192,7 @@ export class BrowserEditor extends EditorPane {
private readonly _inputDisposables = this._register(new DisposableStore());
private overlayManager: BrowserOverlayManager | undefined;
private _elementSelectionCts: CancellationTokenSource | undefined;
private _screenshotTimeout: ReturnType<typeof setTimeout> | undefined;
constructor(
group: IEditorGroup,
@@ -344,6 +345,9 @@ export class BrowserEditor extends EditorPane {
this.focusUrlInput();
}
// Start / stop screenshots when the model visibility changes
this._inputDisposables.add(this._model.onDidChangeVisibility(() => this.doScreenshot()));
// Listen to model events for UI updates
this._inputDisposables.add(this._model.onDidKeyCommand(keyEvent => {
// Handle like webview does - convert to webview KeyEvent format
@@ -398,16 +402,11 @@ export class BrowserEditor extends EditorPane {
this.layoutBrowserContainer();
}
}));
// Capture screenshot periodically (once per second) to keep background updated
this._inputDisposables.add(disposableWindowInterval(
this.window,
() => this.capturePlaceholderSnapshot(),
1000
));
this.updateErrorDisplay();
this.layoutBrowserContainer();
await this._model.setVisible(this.shouldShowView);
this.updateVisibility();
this.doScreenshot();
}
protected override setEditorVisible(visible: boolean): void {
@@ -442,14 +441,26 @@ export class BrowserEditor extends EditorPane {
if (this._model) {
const show = this.shouldShowView;
void this._model.setVisible(show);
if (
show &&
this._browserContainer.ownerDocument.hasFocus() &&
this._browserContainer.ownerDocument.activeElement === this._browserContainer
) {
// If the editor is focused, ensure the browser view also gets focus
void this._model.focus();
if (show === this._model.visible) {
return;
}
if (show) {
this._model.setVisible(true);
if (
this._browserContainer.ownerDocument.hasFocus() &&
this._browserContainer.ownerDocument.activeElement === this._browserContainer
) {
// If the editor is focused, ensure the browser view also gets focus
void this._model.focus();
}
} else {
this.doScreenshot();
// Hide the browser view just before the next render.
// This attempts to give the screenshot some time to be captured and displayed.
// If we hide immediately it is more likely to flicker while the old screenshot is still visible.
this.window.requestAnimationFrame(() => this._model?.setVisible(false));
}
}
}
@@ -780,17 +791,35 @@ export class BrowserEditor extends EditorPane {
}
}
/**
* Capture a screenshot of the current browser view to use as placeholder background
*/
private async capturePlaceholderSnapshot(): Promise<void> {
if (this._model && !this._overlayVisible) {
try {
const buffer = await this._model.captureScreenshot({ quality: 80 });
this.setBackgroundImage(buffer);
} catch (error) {
this.logService.error('BrowserEditor.capturePlaceholderSnapshot: Failed to capture screenshot', error);
}
private async doScreenshot(): Promise<void> {
if (!this._model) {
return;
}
// Cancel any existing timeout
this.cancelScheduledScreenshot();
// Only take screenshots if the model is visible
if (!this._model.visible) {
return;
}
try {
// Capture screenshot and set as background image
const screenshot = await this._model.captureScreenshot({ quality: 80 });
this.setBackgroundImage(screenshot);
} catch (error) {
this.logService.error('Failed to capture browser view screenshot', error);
}
// Schedule next screenshot in 1 second
this._screenshotTimeout = setTimeout(() => this.doScreenshot(), 1000);
}
private cancelScheduledScreenshot(): void {
if (this._screenshotTimeout) {
clearTimeout(this._screenshotTimeout);
this._screenshotTimeout = undefined;
}
}
@@ -859,6 +888,9 @@ export class BrowserEditor extends EditorPane {
this._elementSelectionCts = undefined;
}
// Cancel any scheduled screenshots
this.cancelScheduledScreenshot();
// Clear find widget model
this._findWidget.rawValue?.setModel(undefined);
this._findWidget.rawValue?.hide();
@@ -72,15 +72,19 @@
justify-content: center;
pointer-events: none;
color: var(--vscode-foreground);
background-color: color-mix(in srgb, var(--vscode-editor-background) 60%, transparent);
background-color: color-mix(in srgb, var(--vscode-editor-background) 15%, transparent);
opacity: 0;
visibility: hidden;
transition: opacity 200ms ease-out;
transition: opacity 200ms ease-in;
&.visible {
opacity: 1;
visibility: visible;
}
&.show-message {
background-color: color-mix(in srgb, var(--vscode-editor-background) 60%, transparent);
}
}
.browser-overlay-paused-message {
@@ -1283,26 +1283,3 @@ registerAction2(class EditToolApproval extends Action2 {
confirmationService.manageConfirmationPreferences([...toolsService.getAllToolsIncludingDisabled()], scope ? { defaultScope: scope } : undefined);
}
});
registerAction2(class ToggleChatViewTitleAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.toggleChatViewTitle',
title: localize2('chat.toggleChatViewTitle.label', "Show Chat Title"),
toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewTitleEnabled}`, true),
menu: {
id: MenuId.ChatWelcomeContext,
group: '1_modify',
order: 2,
when: ChatContextKeys.inChatEditor.negate()
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const configurationService = accessor.get(IConfigurationService);
const chatViewTitleEnabled = configurationService.getValue<boolean>(ChatConfiguration.ChatViewTitleEnabled);
await configurationService.updateValue(ChatConfiguration.ChatViewTitleEnabled, !chatViewTitleEnabled);
}
});
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { Schemas } from '../../../../../base/common/network.js';
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { localize2 } from '../../../../../nls.js';
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
@@ -38,15 +39,20 @@ function encodePathForMarkdown(path: string): string {
* The returned path is URL encoded for use in markdown link targets.
*/
function getRelativePath(uri: URI, workspaceFolders: readonly IWorkspaceFolder[]): string {
// On desktop, vscode-userdata scheme maps 1:1 to file scheme paths via FileUserDataProvider.
// Convert to file scheme so relativePath() can compute paths correctly.
// On web, vscode-userdata uses IndexedDB so this conversion has no effect (different schemes won't match workspace folders).
const normalizedUri = uri.scheme === Schemas.vscodeUserData ? uri.with({ scheme: Schemas.file }) : uri;
for (const folder of workspaceFolders) {
const relative = relativePath(folder.uri, uri);
const relative = relativePath(folder.uri, normalizedUri);
if (relative) {
return encodePathForMarkdown(relative);
}
}
// Fall back to fsPath if not under any workspace folder
// Use forward slashes for consistency in markdown links
return encodePathForMarkdown(uri.fsPath.replace(/\\/g, '/'));
return encodePathForMarkdown(normalizedUri.fsPath.replace(/\\/g, '/'));
}
// Tree prefixes
@@ -117,8 +123,13 @@ export function registerChatCustomizationDiagnosticsAction() {
}, {
id: CHAT_CONFIG_MENU_ID,
when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)),
order: 20,
order: 14,
group: '3_configure'
}, {
id: MenuId.ChatWelcomeContext,
group: '2_settings',
order: 0,
when: ChatContextKeys.inChatEditor.negate()
}]
});
}
@@ -423,12 +434,14 @@ export function formatStatusOutput(
// Count loaded and skipped files (overwritten counts as skipped)
let loadedCount = info.files.filter(f => f.status === 'loaded').length;
const skippedCount = info.files.filter(f => f.status === 'skipped' || f.status === 'overwritten').length;
// Include special files in the loaded count
if (info.type === PromptsType.agent && specialFiles.agentsMd.enabled) {
loadedCount += specialFiles.agentsMd.files.length;
}
if (info.type === PromptsType.instructions && specialFiles.copilotInstructions.enabled) {
loadedCount += specialFiles.copilotInstructions.files.length;
// Include special files in the loaded count for instructions
if (info.type === PromptsType.instructions) {
if (specialFiles.agentsMd.enabled) {
loadedCount += specialFiles.agentsMd.files.length;
}
if (specialFiles.copilotInstructions.enabled) {
loadedCount += specialFiles.copilotInstructions.files.length;
}
}
lines.push(`**${typeName}**${enabledStatus}<br>`);
@@ -558,8 +571,9 @@ export function formatStatusOutput(
hasContent = true;
}
// Add special files for agents (AGENTS.md)
if (info.type === PromptsType.agent) {
// Add special files for instructions (AGENTS.md and copilot-instructions.md)
if (info.type === PromptsType.instructions) {
// AGENTS.md
if (specialFiles.agentsMd.enabled && specialFiles.agentsMd.files.length > 0) {
lines.push(`AGENTS.md<br>`);
for (let i = 0; i < specialFiles.agentsMd.files.length; i++) {
@@ -575,10 +589,8 @@ export function formatStatusOutput(
lines.push(`AGENTS.md -<br>`);
hasContent = true;
}
}
// Add special files for instructions (copilot-instructions.md)
if (info.type === PromptsType.instructions) {
// copilot-instructions.md
if (specialFiles.copilotInstructions.enabled && specialFiles.copilotInstructions.files.length > 0) {
lines.push(`${COPILOT_CUSTOM_INSTRUCTIONS_FILENAME}<br>`);
for (let i = 0; i < specialFiles.copilotInstructions.files.length; i++) {

Some files were not shown because too many files have changed in this diff Show More