mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
Merge branch 'main' into tyriar/287652_2
This commit is contained in:
@@ -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
@@ -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'))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
Generated
+3
-3
@@ -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": {
|
||||
|
||||
@@ -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');
|
||||
|
||||
Generated
+125
-240
@@ -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
@@ -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",
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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 }[]> {
|
||||
|
||||
Vendored
+1
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
Generated
+12
-12
@@ -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
@@ -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",
|
||||
|
||||
Generated
+4
-4
@@ -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
@@ -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",
|
||||
|
||||
Generated
+4
-4
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -173,6 +173,7 @@ export class Toggle extends Widget {
|
||||
this.checked = !this._checked;
|
||||
this._onChange.fire(false);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -59,5 +59,4 @@ export interface IDefaultAccount {
|
||||
readonly sessionId: string;
|
||||
readonly enterprise: boolean;
|
||||
readonly entitlementsData?: IEntitlementsData | null;
|
||||
readonly policyData?: IPolicyData;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
+26
-14
@@ -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
Reference in New Issue
Block a user