diff --git a/.eslintignore b/.eslintignore
index 8b93a4199e5..6d76bb78eb7 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -16,3 +16,4 @@
**/extensions/markdown-language-features/notebook-out/**
**/extensions/typescript-basics/test/colorize-fixtures/**
**/extensions/**/dist/**
+**/extensions/typescript-language-features/test-workspace/**
diff --git a/.eslintrc.json b/.eslintrc.json
index 51e623e8f1b..0983efcd74f 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -7,7 +7,8 @@
},
"plugins": [
"@typescript-eslint",
- "jsdoc"
+ "jsdoc",
+ "header"
],
"rules": {
"constructor-super": "warn",
@@ -979,6 +980,16 @@
"xterm*"
]
}
+ ],
+ "header/header": [
+ 2,
+ "block",
+ [
+ "---------------------------------------------------------------------------------------------",
+ " * Copyright (c) Microsoft Corporation. All rights reserved.",
+ " * Licensed under the MIT License. See License.txt in the project root for license information.",
+ " *--------------------------------------------------------------------------------------------"
+ ]
]
},
"overrides": [
diff --git a/.github/classifier.json b/.github/classifier.json
index fd27dc85ec6..2c4808df464 100644
--- a/.github/classifier.json
+++ b/.github/classifier.json
@@ -64,12 +64,12 @@
"extensions": {"assign": ["sandy081"]},
"extensions-development": {"assign": []},
"file-decorations": {"assign": ["jrieken"]},
- "file-encoding": {"assign": ["bpasero"]},
+ "file-encoding": {"assign": []},
"file-explorer": {"assign": ["isidorn"]},
"file-glob": {"assign": []},
- "file-guess-encoding": {"assign": ["bpasero"]},
- "file-io": {"assign": ["bpasero"]},
- "file-watcher": {"assign": ["bpasero"]},
+ "file-guess-encoding": {"assign": []},
+ "file-io": {"assign": []},
+ "file-watcher": {"assign": []},
"font-rendering": {"assign": []},
"formatting": {"assign": []},
"git": {"assign": ["eamodio"]},
@@ -150,31 +150,31 @@
"ux": {"assign": ["misolori"]},
"variable-resolving": {"assign": []},
"vscode-build": {"assign": []},
- "web": {"assign": ["bpasero"]},
+ "web": {"assign": []},
"webview": {"assign": ["mjbvz"]},
"workbench-cli": {"assign": []},
"workbench-diagnostics": {"assign": ["Tyriar"]},
- "workbench-dnd": {"assign": ["bpasero"]},
+ "workbench-dnd": {"assign": []},
"workbench-editor-grid": {"assign": ["sbatten"]},
- "workbench-editors": {"assign": ["bpasero"]},
+ "workbench-editors": {"assign": []},
"workbench-electron": {"assign": ["deepak1556"]},
- "workbench-feedback": {"assign": ["bpasero"]},
- "workbench-history": {"assign": ["bpasero"]},
+ "workbench-feedback": {"assign": []},
+ "workbench-history": {"assign": []},
"workbench-hot-exit": {"assign": []},
"workbench-launch": {"assign": []},
"workbench-link": {"assign": []},
- "workbench-multiroot": {"assign": ["bpasero"]},
- "workbench-notifications": {"assign": ["bpasero"]},
+ "workbench-multiroot": {"assign": []},
+ "workbench-notifications": {"assign": []},
"workbench-os-integration": {"assign": []},
"workbench-rapid-render": {"assign": ["jrieken"]},
"workbench-run-as-admin": {"assign": []},
- "workbench-state": {"assign": ["bpasero"]},
- "workbench-status": {"assign": ["bpasero"]},
- "workbench-tabs": {"assign": ["bpasero"]},
- "workbench-touchbar": {"assign": ["bpasero"]},
+ "workbench-state": {"assign": []},
+ "workbench-status": {"assign": []},
+ "workbench-tabs": {"assign": []},
+ "workbench-touchbar": {"assign": []},
"workbench-views": {"assign": ["sbatten"]},
"workbench-welcome": {"assign": ["JacksonKearl"]},
- "workbench-window": {"assign": ["bpasero"]},
+ "workbench-window": {"assign": []},
"workbench-zen": {"assign": ["isidorn"]},
"workspace-edit": {"assign": ["jrieken"]},
"workspace-symbols": {"assign": []},
diff --git a/.github/workflows/create-codespaces-prebuild.yml b/.github/workflows/create-codespaces-prebuild.yml
index 4ebac2c4daf..5778874313a 100644
--- a/.github/workflows/create-codespaces-prebuild.yml
+++ b/.github/workflows/create-codespaces-prebuild.yml
@@ -11,11 +11,11 @@ jobs:
run: |
$splat = @{
ErrorAction = 'Stop'
- Uri = 'https://api.github.com/vscs_internal/user/vscode-triage-bot/codespaces/prebuild'
+ Uri = 'https://api.github.com/vscs_internal/user/TylerLeonhardt/codespaces/prebuild'
Method = 'POST'
Headers = @{
'Content-Type' = 'application/json; charset=utf-8'
- 'Authorization' = 'token ${{ secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT }}'
+ 'Authorization' = 'token ${{ secrets.CODESPACES_PREBUILD_PAT }}'
}
Body = @{
ref = 'main'
diff --git a/.github/workflows/devcontainer-cache.yml b/.github/workflows/devcontainer-cache.yml
index 87041953080..4bb96145b02 100644
--- a/.github/workflows/devcontainer-cache.yml
+++ b/.github/workflows/devcontainer-cache.yml
@@ -1,6 +1,7 @@
name: VS Code Repo Dev Container Cache Image Generation
on:
+ workflow_dispatch:
push:
# Currently doing this for main, but could be done for PRs as well
branches:
diff --git a/.gitignore b/.gitignore
index 11a7486bf53..95f843584c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ test-results/
yarn-error.log
vscode.lsif
vscode.db
+/.profile-oss
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 4e450e9e696..8f7e5726510 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -16,7 +16,11 @@
"request": "attach",
"restart": true,
"name": "Attach to Extension Host",
- "timeout": 30000,
+ // set to a large number: if there is an issue we're debugging that keeps
+ // the extension host from coming up, or the renderer is paused/crashes
+ // before it happens, developers will get an annoying alert, e.g. #126826.
+ // This can be set to 0 in 1.59.
+ "timeout": 999999999,
"port": 5870,
"outFiles": [
"${workspaceFolder}/out/**/*.js",
@@ -219,7 +223,7 @@
"cascadeTerminateToConfigurations": [
"Attach to Extension Host"
],
- "userDataDir": false,
+ "userDataDir": "${workspaceFolder}/.profile-oss",
"pauseForSourceMap": false,
"outFiles": [
"${workspaceFolder}/out/**/*.js"
diff --git a/.vscode/settings.json b/.vscode/settings.json
index a97841683c0..2c8dce10702 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,6 +4,7 @@
"files.exclude": {
".git": true,
".build": true,
+ ".profile-oss": true,
"**/.DS_Store": true,
"build/**/*.js": {
"when": "$(basename).ts"
diff --git a/build/azure-pipelines/common/sign-win32.ts b/build/azure-pipelines/common/sign-win32.ts
new file mode 100644
index 00000000000..d5daa875812
--- /dev/null
+++ b/build/azure-pipelines/common/sign-win32.ts
@@ -0,0 +1,17 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { main } from './sign';
+import * as path from 'path';
+
+main([
+ process.env['EsrpCliDllPath']!,
+ 'windows',
+ process.env['ESRPPKI']!,
+ process.env['ESRPAADUsername']!,
+ process.env['ESRPAADPassword']!,
+ path.dirname(process.argv[2]),
+ path.basename(process.argv[2])
+]);
diff --git a/build/azure-pipelines/common/sign.ts b/build/azure-pipelines/common/sign.ts
new file mode 100644
index 00000000000..e03682deb4b
--- /dev/null
+++ b/build/azure-pipelines/common/sign.ts
@@ -0,0 +1,84 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as cp from 'child_process';
+import * as fs from 'fs';
+import * as tmp from 'tmp';
+import * as crypto from 'crypto';
+
+function getParams(type: string): string {
+ switch (type) {
+ case 'windows':
+ return '[{"keyCode":"CP-230012","operationSetCode":"SigntoolSign","parameters":[{"parameterName":"OpusName","parameterValue":"VS Code"},{"parameterName":"OpusInfo","parameterValue":"https://code.visualstudio.com/"},{"parameterName":"Append","parameterValue":"/as"},{"parameterName":"FileDigest","parameterValue":"/fd \\"SHA256\\""},{"parameterName":"PageHash","parameterValue":"/NPH"},{"parameterName":"TimeStamp","parameterValue":"/tr \\"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\\" /td sha256"}],"toolName":"sign","toolVersion":"1.0"},{"keyCode":"CP-230012","operationSetCode":"SigntoolVerify","parameters":[{"parameterName":"VerifyAll","parameterValue":"/all"}],"toolName":"sign","toolVersion":"1.0"}]';
+ case 'rpm':
+ return '[{ "keyCode": "CP-450779-Pgp", "operationSetCode": "LinuxSign", "parameters": [], "toolName": "sign", "toolVersion": "1.0" }]';
+ case 'darwin-sign':
+ return '[{"keyCode":"CP-401337-Apple","operationSetCode":"MacAppDeveloperSign","parameters":[{"parameterName":"Hardening","parameterValue":"--options=runtime"}],"toolName":"sign","toolVersion":"1.0"}]';
+ case 'darwin-notarize':
+ return '[{"keyCode":"CP-401337-Apple","operationSetCode":"MacAppNotarize","parameters":[{"parameterName":"BundleId","parameterValue":"$(BundleIdentifier)"}],"toolName":"sign","toolVersion":"1.0"}]';
+ default:
+ throw new Error(`Sign type ${type} not found`);
+ }
+}
+
+export function main([esrpCliPath, type, cert, username, password, folderPath, pattern]: string[]) {
+ tmp.setGracefulCleanup();
+
+ const patternPath = tmp.tmpNameSync();
+ fs.writeFileSync(patternPath, pattern);
+
+ const paramsPath = tmp.tmpNameSync();
+ fs.writeFileSync(paramsPath, getParams(type));
+
+ const keyFile = tmp.tmpNameSync();
+ const key = crypto.randomBytes(32);
+ const iv = crypto.randomBytes(16);
+ fs.writeFileSync(keyFile, JSON.stringify({ key: key.toString('hex'), iv: iv.toString('hex') }));
+
+ const clientkeyPath = tmp.tmpNameSync();
+ const clientkeyCypher = crypto.createCipheriv('aes-256-cbc', key, iv);
+ let clientkey = clientkeyCypher.update(password, 'utf8', 'hex');
+ clientkey += clientkeyCypher.final('hex');
+ fs.writeFileSync(clientkeyPath, clientkey);
+
+ const clientcertPath = tmp.tmpNameSync();
+ const clientcertCypher = crypto.createCipheriv('aes-256-cbc', key, iv);
+ let clientcert = clientcertCypher.update(cert, 'utf8', 'hex');
+ clientcert += clientcertCypher.final('hex');
+ fs.writeFileSync(clientcertPath, clientcert);
+
+ const args = [
+ esrpCliPath,
+ 'vsts.sign',
+ '-a', username,
+ '-k', clientkeyPath,
+ '-z', clientcertPath,
+ '-f', folderPath,
+ '-p', patternPath,
+ '-u', 'false',
+ '-x', 'regularSigning',
+ '-b', 'input.json',
+ '-l', 'AzSecPack_PublisherPolicyProd.xml',
+ '-y', 'inlineSignParams',
+ '-j', paramsPath,
+ '-c', '9997',
+ '-t', '120',
+ '-g', '10',
+ '-v', 'Tls12',
+ '-s', 'https://api.esrp.microsoft.com/api/v1',
+ '-m', '0',
+ '-o', 'Microsoft',
+ '-i', 'https://www.microsoft.com',
+ '-n', '5',
+ '-r', 'true',
+ '-e', keyFile,
+ ];
+
+ cp.spawnSync('dotnet', args, { stdio: 'inherit' });
+}
+
+if (require.main === module) {
+ main(process.argv.slice(2));
+}
diff --git a/build/azure-pipelines/darwin/product-build-darwin-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-sign.yml
index 05ba689a4cb..8b5dd741b51 100644
--- a/build/azure-pipelines/darwin/product-build-darwin-sign.yml
+++ b/build/azure-pipelines/darwin/product-build-darwin-sign.yml
@@ -8,7 +8,7 @@ steps:
inputs:
azureSubscription: "vscode-builds-subscription"
KeyVaultName: vscode
- SecretsFilter: 'github-distro-mixin-password'
+ SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
- script: |
set -e
@@ -28,12 +28,10 @@ steps:
displayName: Merge distro
- script: |
- pushd build \
- && yarn \
- && npm install -g typescript \
- && tsc azure-pipelines/common/createAsset.ts \
- && popd
- displayName: Restore modules for just build folder and compile it
+ set -e
+ yarn --cwd build
+ yarn --cwd build compile
+ displayName: Compile build tools
- download: current
artifact: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive
@@ -45,28 +43,16 @@ steps:
mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip
displayName: Unzip & move
- - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
+ - task: UseDotNet@2
inputs:
- ConnectedServiceName: "ESRP CodeSign"
- FolderPath: "$(agent.builddirectory)"
- Pattern: "VSCode-darwin-$(VSCODE_ARCH).zip"
- signConfigType: inlineSignParams
- inlineOperation: |
- [
- {
- "keyCode": "CP-401337-Apple",
- "operationSetCode": "MacAppDeveloperSign",
- "parameters": [
- {
- "parameterName": "Hardening",
- "parameterValue": "--options=runtime"
- }
- ],
- "toolName": "sign",
- "toolVersion": "1.0"
- }
- ]
- SessionTimeout: 60
+ version: 2.x
+
+ - task: EsrpClientTool@1
+ displayName: Download ESRPClient
+
+ - script: |
+ set -e
+ node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" darwin-sign $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(agent.builddirectory) VSCode-darwin-$(VSCODE_ARCH).zip
displayName: Codesign
- script: |
@@ -76,29 +62,10 @@ steps:
echo "##vso[task.setvariable variable=BundleIdentifier]$BUNDLE_IDENTIFIER"
displayName: Export bundle identifier
- - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
- inputs:
- ConnectedServiceName: "ESRP CodeSign"
- FolderPath: "$(agent.builddirectory)"
- Pattern: "VSCode-darwin-$(VSCODE_ARCH).zip"
- signConfigType: inlineSignParams
- inlineOperation: |
- [
- {
- "keyCode": "CP-401337-Apple",
- "operationSetCode": "MacAppNotarize",
- "parameters": [
- {
- "parameterName": "BundleId",
- "parameterValue": "$(BundleIdentifier)"
- }
- ],
- "toolName": "sign",
- "toolVersion": "1.0"
- }
- ]
- SessionTimeout: 60
- displayName: Notarization
+ - script: |
+ set -e
+ node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" darwin-notarize $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(agent.builddirectory) VSCode-darwin-$(VSCODE_ARCH).zip
+ displayName: Notarize
- script: |
set -e
diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml
index 408ffa3237a..6a2262330a0 100644
--- a/build/azure-pipelines/linux/product-build-linux.yml
+++ b/build/azure-pipelines/linux/product-build-linux.yml
@@ -12,7 +12,7 @@ steps:
inputs:
azureSubscription: "vscode-builds-subscription"
KeyVaultName: vscode
- SecretsFilter: 'github-distro-mixin-password,builds-docdb-key-readwrite,vscode-storage-key'
+ SecretsFilter: "github-distro-mixin-password,builds-docdb-key-readwrite,vscode-storage-key,ESRP-PKI,esrp-aad-username,esrp-aad-password"
- task: DownloadPipelineArtifact@2
inputs:
@@ -252,30 +252,25 @@ steps:
displayName: Prepare snap package
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- # needed for code signing
- task: UseDotNet@2
- displayName: "Install .NET Core SDK 2.x"
inputs:
version: 2.x
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
- inputs:
- ConnectedServiceName: "ESRP CodeSign"
- FolderPath: ".build/linux/rpm"
- Pattern: "*.rpm"
- signConfigType: inlineSignParams
- inlineOperation: |
- [
- {
- "keyCode": "CP-450779-Pgp",
- "operationSetCode": "LinuxSign",
- "parameters": [ ],
- "toolName": "sign",
- "toolVersion": "1.0"
- }
- ]
- SessionTimeout: 120
+ - task: EsrpClientTool@1
+ displayName: Download ESRPClient
+ condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
+
+ - script: |
+ set -e
+ yarn --cwd build
+ yarn --cwd build compile
+ displayName: Compile build tools
+ condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
+
+ - script: |
+ set -e
+ node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" rpm $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) .build/linux/rpm '*.rpm'
displayName: Codesign rpm
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
diff --git a/build/azure-pipelines/win32/ESRPClient/NuGet.config b/build/azure-pipelines/win32/ESRPClient/NuGet.config
deleted file mode 100644
index 4ef337fa560..00000000000
--- a/build/azure-pipelines/win32/ESRPClient/NuGet.config
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/build/azure-pipelines/win32/ESRPClient/packages.config b/build/azure-pipelines/win32/ESRPClient/packages.config
deleted file mode 100644
index ef586de9762..00000000000
--- a/build/azure-pipelines/win32/ESRPClient/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/build/azure-pipelines/win32/prepare-publish.ps1 b/build/azure-pipelines/win32/prepare-publish.ps1
index f80e1ca0ce9..d2870fe7871 100644
--- a/build/azure-pipelines/win32/prepare-publish.ps1
+++ b/build/azure-pipelines/win32/prepare-publish.ps1
@@ -2,9 +2,6 @@
$ErrorActionPreference = "Stop"
$Arch = "$env:VSCODE_ARCH"
-
-exec { yarn gulp "vscode-win32-$Arch-archive" "vscode-win32-$Arch-system-setup" "vscode-win32-$Arch-user-setup" --sign }
-
$Repo = "$(pwd)"
$Root = "$Repo\.."
$SystemExe = "$Repo\.build\win32-$Arch\system-setup\VSCodeSetup.exe"
diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml
index 1331338efbe..6a5bf78daf3 100644
--- a/build/azure-pipelines/win32/product-build-win32.yml
+++ b/build/azure-pipelines/win32/product-build-win32.yml
@@ -17,7 +17,7 @@ steps:
inputs:
azureSubscription: "vscode-builds-subscription"
KeyVaultName: vscode
- SecretsFilter: "github-distro-mixin-password,ESRP-SSL-AADAuth,vscode-storage-key,builds-docdb-key-readwrite"
+ SecretsFilter: "github-distro-mixin-password,vscode-storage-key,builds-docdb-key-readwrite,ESRP-PKI,esrp-aad-username,esrp-aad-password"
- task: DownloadPipelineArtifact@2
inputs:
@@ -247,84 +247,58 @@ steps:
searchFolder: "$(Build.ArtifactStagingDirectory)/test-results"
condition: and(succeededOrFailed(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))
- - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
+ - task: UseDotNet@2
inputs:
- ConnectedServiceName: "ESRP CodeSign"
- FolderPath: "$(CodeSigningFolderPath)"
- Pattern: "*.dll,*.exe,*.node"
- signConfigType: inlineSignParams
- inlineOperation: |
- [
- {
- "keyCode": "CP-230012",
- "operationSetCode": "SigntoolSign",
- "parameters": [
- {
- "parameterName": "OpusName",
- "parameterValue": "VS Code"
- },
- {
- "parameterName": "OpusInfo",
- "parameterValue": "https://code.visualstudio.com/"
- },
- {
- "parameterName": "Append",
- "parameterValue": "/as"
- },
- {
- "parameterName": "FileDigest",
- "parameterValue": "/fd \"SHA256\""
- },
- {
- "parameterName": "PageHash",
- "parameterValue": "/NPH"
- },
- {
- "parameterName": "TimeStamp",
- "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
- }
- ],
- "toolName": "sign",
- "toolVersion": "1.0"
- },
- {
- "keyCode": "CP-230012",
- "operationSetCode": "SigntoolVerify",
- "parameters": [
- {
- "parameterName": "VerifyAll",
- "parameterValue": "/all"
- }
- ],
- "toolName": "sign",
- "toolVersion": "1.0"
- }
- ]
- SessionTimeout: 120
+ version: 2.x
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- - task: NuGetCommand@2
- displayName: Install ESRPClient.exe
- inputs:
- restoreSolution: 'build\azure-pipelines\win32\ESRPClient\packages.config'
- feedsToUse: config
- nugetConfigPath: 'build\azure-pipelines\win32\ESRPClient\NuGet.config'
- externalFeedCredentials: "ESRP Nuget"
- restoreDirectory: packages
+ - task: EsrpClientTool@1
+ displayName: Download ESRPClient
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- - task: ESRPImportCertTask@1
- displayName: Import ESRP Request Signing Certificate
- inputs:
- ESRP: "ESRP CodeSign"
+ - powershell: |
+ . build/azure-pipelines/win32/exec.ps1
+ $ErrorActionPreference = "Stop"
+ exec { yarn --cwd build }
+ exec { yarn --cwd build compile }
+ displayName: Compile build tools
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- - task: PowerShell@2
- inputs:
- targetType: filePath
- filePath: .\build\azure-pipelines\win32\import-esrp-auth-cert.ps1
- arguments: "$(ESRP-SSL-AADAuth)"
- displayName: Import ESRP Auth Certificate
+ - powershell: |
+ . build/azure-pipelines/win32/exec.ps1
+ $ErrorActionPreference = "Stop"
+ $EsrpClientTool = (gci -directory -filter EsrpClientTool_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName
+ $EsrpCliZip = (gci -recurse -filter esrpcli.*.zip $EsrpClientTool | Select-Object -last 1).FullName
+ mkdir -p $(Agent.TempDirectory)\esrpcli
+ Expand-Archive -Path $EsrpCliZip -DestinationPath $(Agent.TempDirectory)\esrpcli
+ $EsrpCliDllPath = (gci -recurse -filter esrpcli.dll $(Agent.TempDirectory)\esrpcli | Select-Object -last 1).FullName
+ echo "##vso[task.setvariable variable=EsrpCliDllPath]$EsrpCliDllPath"
+ displayName: Find ESRP CLI
+ condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
+
+ - powershell: |
+ . build/azure-pipelines/win32/exec.ps1
+ $ErrorActionPreference = "Stop"
+ exec { node build\azure-pipelines\common\sign $env:EsrpCliDllPath windows $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.dll,*.exe,*.node' }
+ displayName: Codesign
+ condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
+
+ - powershell: |
+ . build/azure-pipelines/win32/exec.ps1
+ $ErrorActionPreference = "Stop"
+ exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-archive" }
+ displayName: Package archive
+ condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
+
+ - powershell: |
+ . build/azure-pipelines/win32/exec.ps1
+ $ErrorActionPreference = "Stop"
+ $env:ESRPPKI = "$(ESRP-PKI)"
+ $env:ESRPAADUsername = "$(esrp-aad-username)"
+ $env:ESRPAADPassword = "$(esrp-aad-password)"
+ exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-system-setup" --sign }
+ exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-user-setup" --sign }
+ displayName: Package setups
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- powershell: |
diff --git a/build/azure-pipelines/win32/sign.ps1 b/build/azure-pipelines/win32/sign.ps1
deleted file mode 100644
index b73db31207f..00000000000
--- a/build/azure-pipelines/win32/sign.ps1
+++ /dev/null
@@ -1,71 +0,0 @@
-function Create-TmpJson($Obj) {
- $FileName = [System.IO.Path]::GetTempFileName()
- ConvertTo-Json -Depth 100 $Obj | Out-File -Encoding UTF8 $FileName
- return $FileName
-}
-
-$Auth = Create-TmpJson @{
- Version = "1.0.0"
- AuthenticationType = "AAD_CERT"
- ClientId = $env:ESRPClientId
- AuthCert = @{
- SubjectName = $env:ESRPAuthCertificateSubjectName
- StoreLocation = "LocalMachine"
- StoreName = "My"
- SendX5c = "true"
- }
- RequestSigningCert = @{
- SubjectName = $env:ESRPCertificateSubjectName
- StoreLocation = "LocalMachine"
- StoreName = "My"
- }
-}
-
-$Policy = Create-TmpJson @{
- Version = "1.0.0"
-}
-
-$Input = Create-TmpJson @{
- Version = "1.0.0"
- SignBatches = @(
- @{
- SourceLocationType = "UNC"
- SignRequestFiles = @(
- @{
- SourceLocation = $args[0]
- }
- )
- SigningInfo = @{
- Operations = @(
- @{
- KeyCode = "CP-230012"
- OperationCode = "SigntoolSign"
- Parameters = @{
- OpusName = "VS Code"
- OpusInfo = "https://code.visualstudio.com/"
- Append = "/as"
- FileDigest = "/fd `"SHA256`""
- PageHash = "/NPH"
- TimeStamp = "/tr `"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer`" /td sha256"
- }
- ToolName = "sign"
- ToolVersion = "1.0"
- },
- @{
- KeyCode = "CP-230012"
- OperationCode = "SigntoolVerify"
- Parameters = @{
- VerifyAll = "/all"
- }
- ToolName = "sign"
- ToolVersion = "1.0"
- }
- )
- }
- }
- )
-}
-
-$Output = [System.IO.Path]::GetTempFileName()
-$ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-& "$ScriptPath\ESRPClient\packages\Microsoft.ESRPClient.*\tools\ESRPClient.exe" Sign -a $Auth -p $Policy -i $Input -o $Output
diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js
index 2027dc350cf..0113607a4a9 100644
--- a/build/gulpfile.vscode.win32.js
+++ b/build/gulpfile.vscode.win32.js
@@ -26,7 +26,7 @@ const zipPath = arch => path.join(zipDir(arch), `VSCode-win32-${arch}.zip`);
const setupDir = (arch, target) => path.join(repoPath, '.build', `win32-${arch}`, `${target}-setup`);
const issPath = path.join(__dirname, 'win32', 'code.iss');
const innoSetupPath = path.join(path.dirname(path.dirname(require.resolve('innosetup'))), 'bin', 'ISCC.exe');
-const signPS1 = path.join(repoPath, 'build', 'azure-pipelines', 'win32', 'sign.ps1');
+const signWin32Path = path.join(repoPath, 'build', 'azure-pipelines', 'common', 'sign-win32');
function packageInnoSetup(iss, options, cb) {
options = options || {};
@@ -49,7 +49,7 @@ function packageInnoSetup(iss, options, cb) {
const args = [
iss,
...defs,
- `/sesrp=powershell.exe -ExecutionPolicy bypass ${signPS1} $f`
+ `/sesrp=node ${signWin32Path} $f`
];
cp.spawn(innoSetupPath, args, { stdio: ['ignore', 'inherit', 'inherit'] })
diff --git a/build/package.json b/build/package.json
index 43094626463..7fb5f09c70a 100644
--- a/build/package.json
+++ b/build/package.json
@@ -8,6 +8,7 @@
"@types/ansi-colors": "^3.2.0",
"@types/azure": "0.9.19",
"@types/byline": "^4.2.32",
+ "@types/cssnano": "^4.0.0",
"@types/debounce": "^1.0.0",
"@types/eslint": "4.16.1",
"@types/fancy-log": "^1.3.0",
@@ -17,6 +18,7 @@
"@types/gulp-filter": "^3.0.32",
"@types/gulp-gzip": "^0.0.31",
"@types/gulp-json-editor": "^2.2.31",
+ "@types/gulp-postcss": "^8.0.0",
"@types/gulp-rename": "^0.0.33",
"@types/gulp-sourcemaps": "^0.0.32",
"@types/mime": "0.0.29",
@@ -32,7 +34,9 @@
"@types/rimraf": "^2.0.4",
"@types/through": "^0.0.29",
"@types/through2": "^2.0.34",
+ "@types/tmp": "^0.2.1",
"@types/underscore": "^1.8.9",
+ "@types/webpack": "^4.41.25",
"@types/xml2js": "0.0.33",
"@typescript-eslint/experimental-utils": "~2.13.0",
"@typescript-eslint/parser": "^3.3.0",
@@ -52,6 +56,7 @@
"p-limit": "^3.1.0",
"plist": "^3.0.1",
"source-map": "0.6.1",
+ "tmp": "^0.2.1",
"typescript": "^4.4.0-dev.20210708",
"vsce": "1.48.0",
"vscode-universal": "deepak1556/universal#61454d96223b774c53cda10f72c2098c0ce02d58"
diff --git a/build/yarn.lock b/build/yarn.lock
index 8ca22bcadc3..3a23c6c2a58 100644
--- a/build/yarn.lock
+++ b/build/yarn.lock
@@ -186,6 +186,13 @@
"@types/events" "*"
"@types/node" "*"
+"@types/cssnano@^4.0.0":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.1.tgz#67fa912753d80973a016e7684a47fedf338aacff"
+ integrity sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q==
+ dependencies:
+ postcss "5 - 7"
+
"@types/debounce@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7"
@@ -286,6 +293,14 @@
"@types/js-beautify" "*"
"@types/node" "*"
+"@types/gulp-postcss@^8.0.0":
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/@types/gulp-postcss/-/gulp-postcss-8.0.0.tgz#f7e86d45e4999fd43e6d8c55b00504c88a67ad61"
+ integrity sha512-AVgjA03bpkYONKZpzuJviB9PzaNbDzrovYPbenj8/XxivUc35C/dIzJanyaQv7CFqfLLPLsqSalmtP3GLq6iag==
+ dependencies:
+ "@types/node" "*"
+ "@types/vinyl" "*"
+
"@types/gulp-rename@^0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/gulp-rename/-/gulp-rename-0.0.33.tgz#38d146e97786569f74f5391a1b1f9b5198674b6c"
@@ -428,6 +443,16 @@
"@types/glob" "*"
"@types/node" "*"
+"@types/source-list-map@*":
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
+ integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
+
+"@types/tapable@^1":
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
+ integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==
+
"@types/through2@^2.0.34":
version "2.0.34"
resolved "https://registry.yarnpkg.com/@types/through2/-/through2-2.0.34.tgz#9c2a259a238dace2a05a2f8e94b786961bc27ac4"
@@ -442,6 +467,11 @@
dependencies:
"@types/node" "*"
+"@types/tmp@^0.2.1":
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.1.tgz#83ecf4ec22a8c218c71db25f316619fe5b986011"
+ integrity sha512-7cTXwKP/HLOPVgjg+YhBdQ7bMiobGMuoBmrGmqwIWJv8elC6t1DfVc/mn4fD9UE1IjhwmhaQ5pGVXkmXbH0rhg==
+
"@types/tough-cookie@*":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.2.tgz#e0d481d8bb282ad8a8c9e100ceb72c995fb5e709"
@@ -454,6 +484,13 @@
dependencies:
"@types/node" "*"
+"@types/uglify-js@*":
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea"
+ integrity sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==
+ dependencies:
+ source-map "^0.6.1"
+
"@types/underscore@^1.8.9":
version "1.8.9"
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.9.tgz#fef41f800cd23db1b4f262ddefe49cd952d82323"
@@ -489,6 +526,27 @@
dependencies:
"@types/node" "*"
+"@types/webpack-sources@*":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.1.tgz#6af17e3a3ded71eec2b98008d7c12f498a0a4506"
+ integrity sha512-MjM1R6iuw8XaVbtkCBz0N349cyqBjJHCbQiOeppe3VBeFvxqs74RKHAVt9LkxTnUWc7YLZOEsUfPUnmK6SBPKQ==
+ dependencies:
+ "@types/node" "*"
+ "@types/source-list-map" "*"
+ source-map "^0.7.3"
+
+"@types/webpack@^4.41.25":
+ version "4.41.30"
+ resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.30.tgz#fd3db6d0d41e145a8eeeafcd3c4a7ccde9068ddc"
+ integrity sha512-GUHyY+pfuQ6haAfzu4S14F+R5iGRwN6b2FRNJY7U0NilmFAqbsOfK6j1HwuLBAqwRIT+pVdNDJGJ6e8rpp0KHA==
+ dependencies:
+ "@types/node" "*"
+ "@types/tapable" "^1"
+ "@types/uglify-js" "*"
+ "@types/webpack-sources" "*"
+ anymatch "^3.0.0"
+ source-map "^0.6.0"
+
"@types/xml2js@0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.0.33.tgz#20c5dd6460245284d64a55690015b95e409fb7de"
@@ -574,6 +632,21 @@ ajv@^6.12.3:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+anymatch@^3.0.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
+ integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
applicationinsights@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
@@ -747,6 +820,15 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
+chalk@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
cheerio@^1.0.0-rc.1:
version "1.0.0-rc.2"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
@@ -771,6 +853,18 @@ clone-response@^1.0.2:
dependencies:
mimic-response "^1.0.0"
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+
colors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
@@ -997,6 +1091,11 @@ esbuild@^0.12.6:
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.6.tgz#85bc755c7cf3005d4f34b4f10f98049ce0ee67ce"
integrity sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA==
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
eslint-scope@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
@@ -1130,6 +1229,18 @@ glob@^7.0.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@^7.1.3:
+ version "7.1.7"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
+ integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -1182,6 +1293,11 @@ har-validator@~5.1.3:
ajv "^6.12.3"
har-schema "^2.0.0"
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
hash-base@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
@@ -1487,6 +1603,11 @@ node-fetch@^2.6.0:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
+normalize-path@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
normalize-url@^4.1.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
@@ -1575,6 +1696,11 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
+picomatch@^2.0.4:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
+ integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
+
plist@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c"
@@ -1584,6 +1710,15 @@ plist@^3.0.1:
xmlbuilder "^9.0.7"
xmldom "0.1.x"
+"postcss@5 - 7":
+ version "7.0.36"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb"
+ integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==
+ dependencies:
+ chalk "^2.4.2"
+ source-map "^0.6.1"
+ supports-color "^6.1.0"
+
priorityqueuejs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8"
@@ -1707,6 +1842,13 @@ responselike@^2.0.0:
dependencies:
lowercase-keys "^2.0.0"
+rimraf@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@@ -1766,11 +1908,16 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-source-map@0.6.1:
+source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+source-map@^0.7.3:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
+ integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
+
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -1803,6 +1950,20 @@ string_decoder@~0.10.x:
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+supports-color@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+ integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+ dependencies:
+ has-flag "^3.0.0"
+
tmp@0.0.29:
version "0.0.29"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0"
@@ -1810,6 +1971,13 @@ tmp@0.0.29:
dependencies:
os-tmpdir "~1.0.1"
+tmp@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
+ integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
+ dependencies:
+ rimraf "^3.0.0"
+
tough-cookie@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
diff --git a/extensions/git/package.json b/extensions/git/package.json
index 55f83ea6a89..e8f969896db 100644
--- a/extensions/git/package.json
+++ b/extensions/git/package.json
@@ -577,7 +577,7 @@
},
{
"command": "git.openChange",
- "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
+ "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourcePath in git.changedResources"
},
{
"command": "git.stage",
@@ -1324,7 +1324,7 @@
{
"command": "git.openChange",
"group": "navigation",
- "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file"
+ "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file && resourcePath in git.changedResources"
},
{
"command": "git.stageSelectedRanges",
diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts
index 37c0eb94db4..0f4220e3649 100644
--- a/extensions/git/src/repository.ts
+++ b/extensions/git/src/repository.ts
@@ -1847,6 +1847,7 @@ export class Repository implements Disposable {
this._submodules = submodules!;
this.rebaseCommit = rebaseCommit;
+
const untrackedChanges = scopedConfig.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges');
const index: Resource[] = [];
const workingTree: Resource[] = [];
@@ -1905,6 +1906,9 @@ export class Repository implements Disposable {
// set count badge
this.setCountBadge();
+ // Update context key with changed resources
+ commands.executeCommand('setContext', 'git.changedResources', workingTree.map(r => r.resourceUri.fsPath.toString()));
+
this._onDidChangeStatus.fire();
this._sourceControl.commitTemplate = await this.getInputTemplate();
diff --git a/package.json b/package.json
index afd88e251c0..ebb843c444c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.59.0",
- "distro": "d3a0cc9d45a73d4486d9969408ce4b21da76844f",
+ "distro": "b10247fd6dfcf2e8ff7fb4772bc58a2e97e108a3",
"author": {
"name": "Microsoft Corporation"
},
@@ -59,6 +59,7 @@
"dependencies": {
"applicationinsights": "1.0.8",
"chokidar": "3.5.1",
+ "eslint-plugin-header": "3.1.1",
"graceful-fs": "4.2.6",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
@@ -81,10 +82,10 @@
"vscode-ripgrep": "^1.12.0",
"vscode-sqlite3": "4.0.11",
"vscode-textmate": "5.4.0",
- "xterm": "4.13.0",
+ "xterm": "4.14.0-beta.5",
"xterm-addon-search": "0.9.0-beta.3",
"xterm-addon-unicode11": "0.3.0-beta.5",
- "xterm-addon-webgl": "0.12.0-beta.2",
+ "xterm-addon-webgl": "0.12.0-beta.4",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},
@@ -224,4 +225,4 @@
"elliptic": "^6.5.3",
"nwmatcher": "^1.4.4"
}
-}
+}
\ No newline at end of file
diff --git a/remote/package.json b/remote/package.json
index cd726c3cf8d..2abc1e24cab 100644
--- a/remote/package.json
+++ b/remote/package.json
@@ -22,10 +22,10 @@
"vscode-regexpp": "^3.1.0",
"vscode-ripgrep": "^1.12.0",
"vscode-textmate": "5.4.0",
- "xterm": "4.13.0",
+ "xterm": "4.14.0-beta.5",
"xterm-addon-search": "0.9.0-beta.3",
"xterm-addon-unicode11": "0.3.0-beta.5",
- "xterm-addon-webgl": "0.12.0-beta.2",
+ "xterm-addon-webgl": "0.12.0-beta.4",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},
diff --git a/remote/web/package.json b/remote/web/package.json
index e08e601fbef..c3645f182dc 100644
--- a/remote/web/package.json
+++ b/remote/web/package.json
@@ -8,9 +8,9 @@
"tas-client-umd": "0.1.4",
"vscode-oniguruma": "1.5.1",
"vscode-textmate": "5.4.0",
- "xterm": "4.13.0",
+ "xterm": "4.14.0-beta.5",
"xterm-addon-search": "0.9.0-beta.3",
"xterm-addon-unicode11": "0.3.0-beta.5",
- "xterm-addon-webgl": "0.12.0-beta.2"
+ "xterm-addon-webgl": "0.12.0-beta.4"
}
}
diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock
index b3de3a15a5f..8309d8bf9b9 100644
--- a/remote/web/yarn.lock
+++ b/remote/web/yarn.lock
@@ -37,12 +37,12 @@ xterm-addon-unicode11@0.3.0-beta.5:
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.5.tgz#7e490799d530d3b301125c7a4e92317c161761c4"
integrity sha512-SgDDL3PoMH1G48JO6T45whKAex4NPxi80UzUVitnrqyd8dFQP+oF6cxqUutULgm9HSGk62qy3mrZvIMGO5VXog==
-xterm-addon-webgl@0.12.0-beta.2:
- version "0.12.0-beta.2"
- resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.2.tgz#f9b22c564096629544f78f8fb83f36605e6f37dc"
- integrity sha512-v0fiEFLSBSvIw0Be+ddPmtH3LqLJwzIDRArubCPazOVLpS4paFHWyq61cmDZ87Lnb3G+ppMntsocrt273sFYQg==
+xterm-addon-webgl@0.12.0-beta.4:
+ version "0.12.0-beta.4"
+ resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.4.tgz#f53d36f2f9c2dc6f6fc040796852c50d4384b351"
+ integrity sha512-EcxG773wWzlwbowd3bF30mHb/j2ZhHHwHfGutdpQEL3hGBhCqCZOKBqJ/1yRd2N/wKxXEKDfpJt5g5nssiahfw==
-xterm@4.13.0:
- version "4.13.0"
- resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.13.0.tgz#7998de1e2ad92c4796fe45807be4f31061f3d9d1"
- integrity sha512-HVW1gdoLOTnkMaqQCr2r3mQy4fX9iSa5gWxKZ2UTYdLa4iqavv7QxJ8n1Ypse32shPVkhTYPLS6vHEFZp5ghzw==
+xterm@4.14.0-beta.5:
+ version "4.14.0-beta.5"
+ resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.14.0-beta.5.tgz#6eb7746c2de288309aa30084ed3bc385901908cc"
+ integrity sha512-31/sF2RdQbuPx1G7ofpWb0FfOCGDdQRC/r7pxH8sifjHiDu0k3jZsHwIuKOsEwVf6COTlBYyvNe3q7Ex1Htl1g==
diff --git a/remote/yarn.lock b/remote/yarn.lock
index 76fa44260a6..fe925880435 100644
--- a/remote/yarn.lock
+++ b/remote/yarn.lock
@@ -539,15 +539,15 @@ xterm-addon-unicode11@0.3.0-beta.5:
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.5.tgz#7e490799d530d3b301125c7a4e92317c161761c4"
integrity sha512-SgDDL3PoMH1G48JO6T45whKAex4NPxi80UzUVitnrqyd8dFQP+oF6cxqUutULgm9HSGk62qy3mrZvIMGO5VXog==
-xterm-addon-webgl@0.12.0-beta.2:
- version "0.12.0-beta.2"
- resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.2.tgz#f9b22c564096629544f78f8fb83f36605e6f37dc"
- integrity sha512-v0fiEFLSBSvIw0Be+ddPmtH3LqLJwzIDRArubCPazOVLpS4paFHWyq61cmDZ87Lnb3G+ppMntsocrt273sFYQg==
+xterm-addon-webgl@0.12.0-beta.4:
+ version "0.12.0-beta.4"
+ resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.4.tgz#f53d36f2f9c2dc6f6fc040796852c50d4384b351"
+ integrity sha512-EcxG773wWzlwbowd3bF30mHb/j2ZhHHwHfGutdpQEL3hGBhCqCZOKBqJ/1yRd2N/wKxXEKDfpJt5g5nssiahfw==
-xterm@4.13.0:
- version "4.13.0"
- resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.13.0.tgz#7998de1e2ad92c4796fe45807be4f31061f3d9d1"
- integrity sha512-HVW1gdoLOTnkMaqQCr2r3mQy4fX9iSa5gWxKZ2UTYdLa4iqavv7QxJ8n1Ypse32shPVkhTYPLS6vHEFZp5ghzw==
+xterm@4.14.0-beta.5:
+ version "4.14.0-beta.5"
+ resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.14.0-beta.5.tgz#6eb7746c2de288309aa30084ed3bc385901908cc"
+ integrity sha512-31/sF2RdQbuPx1G7ofpWb0FfOCGDdQRC/r7pxH8sifjHiDu0k3jZsHwIuKOsEwVf6COTlBYyvNe3q7Ex1Htl1g==
yauzl@^2.9.2:
version "2.10.0"
diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts
index 68387c77d50..13c5cef9511 100644
--- a/src/vs/base/browser/ui/list/listWidget.ts
+++ b/src/vs/base/browser/ui/list/listWidget.ts
@@ -281,7 +281,7 @@ class KeyboardController implements IDisposable {
this.onKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDownArrow, this, this.disposables);
this.onKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(this.onEscape, this, this.disposables);
- if (options.multipleSelectionSupport) {
+ if (options.multipleSelectionSupport !== false) {
this.onKeyDown.filter(e => (platform.isMacintosh ? e.metaKey : e.ctrlKey) && e.keyCode === KeyCode.KEY_A).on(this.onCtrlA, this, this.multipleSelectionDisposables);
}
}
@@ -1346,7 +1346,7 @@ export class List implements ISpliceable, IThemable, IDisposable {
this.ariaLabel = this.accessibilityProvider.getWidgetAriaLabel();
}
- if (this._options.multipleSelectionSupport) {
+ if (this._options.multipleSelectionSupport !== false) {
this.view.domNode.setAttribute('aria-multiselectable', 'true');
}
}
diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts
index d572f52f1f5..9aec2bbceed 100644
--- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts
+++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts
@@ -11,19 +11,22 @@ import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket } from 'vs/bas
import { VSBuffer } from 'vs/base/common/buffer';
import { tmpdir } from 'os';
import product from 'vs/platform/product/common/product';
+import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
+import { Disposable } from 'vs/base/common/lifecycle';
-class MessageStream {
+class MessageStream extends Disposable {
private _currentComplete: ((data: VSBuffer) => void) | null;
private _messages: VSBuffer[];
constructor(x: Protocol | PersistentProtocol) {
+ super();
this._currentComplete = null;
this._messages = [];
- x.onMessage(data => {
+ this._register(x.onMessage(data => {
this._messages.push(data);
this._trigger();
- });
+ }));
}
private _trigger(): void {
@@ -121,6 +124,8 @@ class Ether {
suite('IPC, Socket Protocol', () => {
+ ensureNoDisposablesAreLeakedInTestSuite();
+
let ether: Ether;
setup(() => {
@@ -142,6 +147,10 @@ suite('IPC, Socket Protocol', () => {
a.send(buffer);
const msg2 = await bMessages.waitForOne();
assert.strictEqual(msg2.readUInt8(0), 123);
+
+ bMessages.dispose();
+ a.dispose();
+ b.dispose();
});
@@ -161,11 +170,18 @@ suite('IPC, Socket Protocol', () => {
a.send(VSBuffer.fromString(JSON.stringify(data)));
const msg = await bMessages.waitForOne();
assert.deepStrictEqual(JSON.parse(msg.toString()), data);
+
+ bMessages.dispose();
+ a.dispose();
+ b.dispose();
});
});
suite('PersistentProtocol reconnection', () => {
+
+ ensureNoDisposablesAreLeakedInTestSuite();
+
let ether: Ether;
setup(() => {
@@ -222,6 +238,11 @@ suite('PersistentProtocol reconnection', () => {
assert.strictEqual(b2.toString(), 'a4');
assert.strictEqual(a.unacknowledgedCount, 1);
assert.strictEqual(b.unacknowledgedCount, 0);
+
+ aMessages.dispose();
+ bMessages.dispose();
+ a.dispose();
+ b.dispose();
});
});
diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts
index 2c387dab46f..6472e73dac0 100644
--- a/src/vs/base/test/common/utils.ts
+++ b/src/vs/base/test/common/utils.ts
@@ -96,7 +96,17 @@ class DisposableTracker implements IDisposableTracker {
.filter(v => v.source !== null && !this.getRootParent(v, rootParentCache).isSingleton);
if (leaking.length > 0) {
- throw new Error(`These disposables were not disposed:\n${leaking.map(l => l.source).join('--------------------\n')}`);
+ const count = 10;
+ const firstLeaking = leaking.slice(0, count);
+ const remainingCount = leaking.length - count;
+
+ const separator = '--------------------\n\n';
+ let s = firstLeaking.map(l => l.source).join(separator);
+ if (remainingCount > 0) {
+ s += `${separator}+ ${remainingCount} more`;
+ }
+
+ throw new Error(`These disposables were not disposed:\n${s}`);
}
}
}
diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts
index c3477d25bc7..34f6d6506b3 100644
--- a/src/vs/code/electron-main/main.ts
+++ b/src/vs/code/electron-main/main.ts
@@ -59,6 +59,7 @@ import { cwd } from 'vs/base/common/process';
import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol';
import { ProtocolMainService } from 'vs/platform/protocol/electron-main/protocolMainService';
import { Promises } from 'vs/base/common/async';
+import { toDisposable } from 'vs/base/common/lifecycle';
/**
* The main VS Code entry point.
@@ -339,6 +340,9 @@ class CodeMain {
throw new ExpectedError('Sent env to running instance. Terminating...');
}
+ const lockFile = await this.createLockfile(environmentMainService);
+ once(lifecycleMainService.onWillShutdown)(() => lockFile.dispose());
+
// Print --status usage info
if (environmentMainService.args.status) {
logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
@@ -419,6 +423,18 @@ class CodeMain {
lifecycleMainService.kill(exitCode);
}
+ private async createLockfile(env: IEnvironmentMainService) {
+ await FSPromises.writeFile(env.mainLockfile, String(process.pid));
+
+ return toDisposable(() => {
+ try {
+ unlinkSync(env.mainLockfile);
+ } catch {
+ // ignored
+ }
+ });
+ }
+
//#region Command line arguments utilities
private resolveArgs(): NativeParsedArgs {
diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts
index a8c99f6a0fb..be1f71119d3 100644
--- a/src/vs/editor/browser/controller/mouseTarget.ts
+++ b/src/vs/editor/browser/controller/mouseTarget.ts
@@ -14,7 +14,7 @@ import { Position } from 'vs/editor/common/core/position';
import { Range as EditorRange } from 'vs/editor/common/core/range';
import { HorizontalPosition } from 'vs/editor/common/view/renderingContext';
import { ViewContext } from 'vs/editor/common/view/viewContext';
-import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
+import { InjectedText, IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
import * as dom from 'vs/base/browser/dom';
import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations';
@@ -61,7 +61,8 @@ class ContentHitTestResult {
readonly type = HitTestResultType.Content;
constructor(
readonly position: Position,
- readonly spanNode: HTMLElement
+ readonly spanNode: HTMLElement,
+ readonly injectedText: InjectedText | null,
) { }
}
@@ -71,7 +72,7 @@ namespace HitTestResult {
export function createFromDOMInfo(ctx: HitTestContext, spanNode: HTMLElement, offset: number): HitTestResult {
const position = ctx.getPositionFromDOMInfo(spanNode, offset);
if (position) {
- return new ContentHitTestResult(position, spanNode);
+ return new ContentHitTestResult(position, spanNode, null);
}
return new UnknownHitTestResult(spanNode);
}
@@ -505,7 +506,7 @@ export class MouseTargetFactory {
const hitTestResult = MouseTargetFactory._doHitTest(ctx, request);
if (hitTestResult.type === HitTestResultType.Content) {
- return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position);
+ return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position, hitTestResult.injectedText);
}
return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true);
@@ -702,7 +703,7 @@ export class MouseTargetFactory {
const hitTestResult = MouseTargetFactory._doHitTest(ctx, request);
if (hitTestResult.type === HitTestResultType.Content) {
- return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position);
+ return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position, hitTestResult.injectedText);
}
return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true);
@@ -758,7 +759,7 @@ export class MouseTargetFactory {
return (chars + 1);
}
- private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position): MouseTarget {
+ private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position, injectedText: InjectedText | null): MouseTarget {
const lineNumber = pos.lineNumber;
const column = pos.column;
@@ -778,7 +779,7 @@ export class MouseTargetFactory {
const columnHorizontalOffset = visibleRange.left;
if (request.mouseContentHorizontalOffset === columnHorizontalOffset) {
- return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: false });
+ return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !!injectedText });
}
// Let's define a, b, c and check if the offset is in between them...
@@ -811,10 +812,10 @@ export class MouseTargetFactory {
const curr = points[i];
if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) {
const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column);
- return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode });
+ return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText });
}
}
- return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode });
+ return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText });
}
/**
@@ -957,14 +958,16 @@ export class MouseTargetFactory {
result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates());
}
if (result.type === HitTestResultType.Content) {
+ const injectedText = ctx.model.getInjectedTextAt(result.position);
+
const normalizedPosition = ctx.model.normalizePosition(result.position, PositionAffinity.None);
- if (!normalizedPosition.equals(result.position)) {
- result = new ContentHitTestResult(normalizedPosition, result.spanNode);
+ if (injectedText || !normalizedPosition.equals(result.position)) {
+ result = new ContentHitTestResult(normalizedPosition, result.spanNode, injectedText);
}
}
// Snap to the nearest soft tab boundary if atomic soft tabs are enabled.
if (result.type === HitTestResultType.Content && ctx.stickyTabStops) {
- result = new ContentHitTestResult(this._snapToSoftTabBoundary(result.position, ctx.model), result.spanNode);
+ result = new ContentHitTestResult(this._snapToSoftTabBoundary(result.position, ctx.model), result.spanNode, result.injectedText);
}
return result;
}
diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts
index e33a53efea3..da70d12baf3 100644
--- a/src/vs/editor/common/model.ts
+++ b/src/vs/editor/common/model.ts
@@ -180,6 +180,11 @@ export interface InjectedTextOptions {
* If set, the decoration will be rendered inline with the text with this CSS class name.
*/
readonly inlineClassName?: string | null;
+
+ /**
+ * If there is an `inlineClassName` which affects letter spacing.
+ */
+ readonly inlineClassNameAffectsLetterSpacing?: boolean;
}
/**
diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts
index 747b4becd2b..675bee89ef2 100644
--- a/src/vs/editor/common/model/textModel.ts
+++ b/src/vs/editor/common/model/textModel.ts
@@ -3399,10 +3399,12 @@ export class ModelDecorationInjectedTextOptions implements model.InjectedTextOpt
public readonly content: string;
readonly inlineClassName: string | null;
+ readonly inlineClassNameAffectsLetterSpacing: boolean;
private constructor(options: model.InjectedTextOptions) {
this.content = options.content || '';
this.inlineClassName = options.inlineClassName || null;
+ this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false;
}
}
diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts
index d9863fff7fe..d5284c24f0d 100644
--- a/src/vs/editor/common/modes.ts
+++ b/src/vs/editor/common/modes.ts
@@ -1544,6 +1544,7 @@ export interface AuthenticationSession {
id: string;
}
scopes: ReadonlyArray;
+ idToken?: string;
}
/**
diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts
index 1010899cc61..376e4f280fb 100644
--- a/src/vs/editor/common/viewModel/splitLinesCollection.ts
+++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts
@@ -12,7 +12,7 @@ import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDe
import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
-import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, LineBreakData, SingleLineInlineDecoration, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
+import { ICoordinatesConverter, InjectedText, ILineBreaksComputer, IOverviewRulerDecorations, LineBreakData, SingleLineInlineDecoration, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { EditorTheme } from 'vs/editor/common/view/viewContext';
@@ -48,6 +48,8 @@ export interface ISplitLine {
getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number, affinity?: PositionAffinity): Position;
getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number;
normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position;
+
+ getInjectedTextAt(outputLineIndex: number, column: number): InjectedText | null;
}
export interface IViewModelLinesCollection extends IDisposable {
@@ -78,6 +80,8 @@ export interface IViewModelLinesCollection extends IDisposable {
getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: EditorTheme): IOverviewRulerDecorations;
getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[];
+ getInjectedTextAt(viewPosition: Position): InjectedText | null;
+
normalizePosition(position: Position, affinity: PositionAffinity): Position;
/**
* Gets the column at which indentation stops at a given line.
@@ -883,13 +887,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public convertModelRangeToViewRange(modelRange: Range): Range {
- const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Right);
- let end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn, PositionAffinity.Left);
- if (end.isBefore(start)) {
- // If the range is empty, we don't want the range to get expanded just by converting to a view range
- end = start;
+ if (modelRange.isEmpty()) {
+ const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Left);
+ return Range.fromPositions(start);
+ } else {
+ const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, PositionAffinity.Right);
+ const end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn, PositionAffinity.Left);
+ return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
}
- return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
}
private _getViewLineNumberForModelPosition(inputLineNumber: number, inputColumn: number): number {
@@ -997,6 +1002,15 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return finalResult;
}
+ public getInjectedTextAt(position: Position): InjectedText | null {
+ const viewLineNumber = this._toValidViewLineNumber(position.lineNumber);
+ const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
+ const lineIndex = r.index;
+ const remainder = r.remainder;
+
+ return this.lines[lineIndex].getInjectedTextAt(remainder, position.column);
+ }
+
normalizePosition(position: Position, affinity: PositionAffinity): Position {
const viewLineNumber = this._toValidViewLineNumber(position.lineNumber);
const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
@@ -1101,6 +1115,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
public normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position {
return outputPosition;
}
+
+ public getInjectedTextAt(_outputLineIndex: number, _outputColumn: number): InjectedText | null {
+ return null;
+ }
}
class InvisibleIdentitySplitLine implements ISplitLine {
@@ -1167,6 +1185,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
public normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position {
throw new Error('Not supported');
}
+
+ public getInjectedTextAt(_outputLineIndex: number, _outputColumn: number): InjectedText | null {
+ throw new Error('Not supported');
+ }
}
export class SplitLine implements ISplitLine {
@@ -1224,7 +1246,7 @@ export class SplitLine implements ISplitLine {
let r: string;
if (this._lineBreakData.injectionOffsets !== null) {
- const injectedTexts = this._lineBreakData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset, this._lineBreakData.injectionOptions![idx], 0));
+ const injectedTexts = this._lineBreakData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset + 1, this._lineBreakData.injectionOptions![idx], 0));
r = LineInjectedText.applyInjectedText(model.getLineContent(modelLineNumber), injectedTexts).substring(startOffset, endOffset);
} else {
r = model.getValueInRange({
@@ -1325,13 +1347,13 @@ export class SplitLine implements ISplitLine {
if (lineStartOffsetInUnwrappedLine < injectedTextEndOffsetInUnwrappedLine) {
// Injected text ends after or in this line (but also starts in or before this line).
- const inlineClassName = injectionOptions![i].inlineClassName;
- if (inlineClassName) {
+ const options = injectionOptions![i];
+ if (options.inlineClassName) {
const offset = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0);
const start = offset + Math.max(injectedTextStartOffsetInUnwrappedLine - lineStartOffsetInUnwrappedLine, 0);
const end = offset + Math.min(injectedTextEndOffsetInUnwrappedLine - lineStartOffsetInUnwrappedLine, lineEndOffsetInUnwrappedLine);
if (start !== end) {
- inlineDecorations.push(new SingleLineInlineDecoration(start, end, inlineClassName));
+ inlineDecorations.push(new SingleLineInlineDecoration(start, end, options.inlineClassName, options.inlineClassNameAffectsLetterSpacing!));
}
}
}
@@ -1451,6 +1473,10 @@ export class SplitLine implements ISplitLine {
return outputPosition;
}
+
+ public getInjectedTextAt(outputLineIndex: number, outputColumn: number): InjectedText | null {
+ return this._lineBreakData.getInjectedText(outputLineIndex, outputColumn - 1);
+ }
}
let _spaces: string[] = [''];
@@ -1694,6 +1720,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
public getLineIndentColumn(lineNumber: number): number {
return this.model.getLineIndentColumn(lineNumber);
}
+
+ public getInjectedTextAt(position: Position): InjectedText | null {
+ // Identity lines collection does not support injected text.
+ return null;
+ }
}
class OverviewRulerDecorations {
diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts
index 3b4825d1c1d..c95364e88da 100644
--- a/src/vs/editor/common/viewModel/viewModel.ts
+++ b/src/vs/editor/common/viewModel/viewModel.ts
@@ -205,7 +205,7 @@ export class LineBreakData {
}
public normalizeOffsetAroundInjections(offsetInUnwrappedLine: number, affinity: PositionAffinity): number {
- const injectedText = this.getInjectedTextAt(offsetInUnwrappedLine);
+ const injectedText = this.getInjectedTextAtOffset(offsetInUnwrappedLine);
if (!injectedText) {
return offsetInUnwrappedLine;
}
@@ -242,7 +242,18 @@ export class LineBreakData {
return result;
}
- private getInjectedTextAt(offsetInUnwrappedLine: number): { injectedTextIndex: number, offsetInUnwrappedLine: number, length: number } | undefined {
+ public getInjectedText(outputLineIndex: number, outputOffset: number): InjectedText | null {
+ const offset = this.outputPositionToOffsetInUnwrappedLine(outputLineIndex, outputOffset);
+ const injectedText = this.getInjectedTextAtOffset(offset);
+ if (!injectedText) {
+ return null;
+ }
+ return {
+ options: this.injectionOptions![injectedText.injectedTextIndex]
+ };
+ }
+
+ private getInjectedTextAtOffset(offsetInUnwrappedLine: number): { injectedTextIndex: number, offsetInUnwrappedLine: number, length: number } | undefined {
const injectionOffsets = this.injectionOffsets;
const injectionOptions = this.injectionOptions;
@@ -328,6 +339,8 @@ export interface IViewModel extends ICursorSimpleModel {
invalidateMinimapColorCache(): void;
getValueInRange(range: Range, eol: EndOfLinePreference): string;
+ getInjectedTextAt(viewPosition: Position): InjectedText | null;
+
getModelLineMaxColumn(modelLineNumber: number): number;
validateModelPosition(modelPosition: IPosition): Position;
validateModelRange(range: IRange): Range;
@@ -372,6 +385,10 @@ export interface IViewModel extends ICursorSimpleModel {
//#endregion
}
+export class InjectedText {
+ constructor(public readonly options: InjectedTextOptions) { }
+}
+
export class MinimapLinesRenderingData {
public readonly tabSize: number;
public readonly data: Array;
@@ -540,7 +557,8 @@ export class SingleLineInlineDecoration {
constructor(
public readonly startOffset: number,
public readonly endOffset: number,
- public readonly inlineClassName: string
+ public readonly inlineClassName: string,
+ public readonly inlineClassNameAffectsLetterSpacing: boolean
) {
}
@@ -548,7 +566,7 @@ export class SingleLineInlineDecoration {
return new InlineDecoration(
new Range(lineNumber, this.startOffset + 1, lineNumber, this.endOffset + 1),
this.inlineClassName,
- InlineDecorationType.Regular
+ this.inlineClassNameAffectsLetterSpacing ? InlineDecorationType.RegularAffectingLetterSpacing : InlineDecorationType.Regular
);
}
}
diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts
index dc410ae2ca9..5af54d6c470 100644
--- a/src/vs/editor/common/viewModel/viewModelImpl.ts
+++ b/src/vs/editor/common/viewModel/viewModelImpl.ts
@@ -21,7 +21,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
-import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
+import { ICoordinatesConverter, InjectedText, ILineBreaksComputer, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
import { RunOnceScheduler } from 'vs/base/common/async';
import * as platform from 'vs/base/common/platform';
@@ -652,6 +652,10 @@ export class ViewModel extends Disposable implements IViewModel {
return this._decorations.getDecorationsViewportData(visibleRange).decorations;
}
+ public getInjectedTextAt(viewPosition: Position): InjectedText | null {
+ return this._lines.getInjectedTextAt(viewPosition);
+ }
+
public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData {
let mightContainRTL = this.model.mightContainRTL();
let mightContainNonBasicASCII = this.model.mightContainNonBasicASCII();
diff --git a/src/vs/editor/contrib/inlineCompletions/ghostText.ts b/src/vs/editor/contrib/inlineCompletions/ghostText.ts
index 178431c7b2a..6817c1d24b7 100644
--- a/src/vs/editor/contrib/inlineCompletions/ghostText.ts
+++ b/src/vs/editor/contrib/inlineCompletions/ghostText.ts
@@ -7,6 +7,8 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
+import { Position } from 'vs/editor/common/core/position';
+import { Range, IRange } from 'vs/editor/common/core/range';
export class GhostText {
public static equals(a: GhostText | undefined, b: GhostText | undefined): boolean {
@@ -25,12 +27,62 @@ export class GhostText {
this.parts.length === other.parts.length &&
this.parts.every((part, index) => part.equals(other.parts[index]));
}
+
+ render(text: string, debug: boolean = false): string {
+ const l = this.lineNumber;
+ return applyEdits(text,
+ [
+ ...this.parts.map(p => ({
+ range: { startLineNumber: l, endLineNumber: l, startColumn: p.column, endColumn: p.column },
+ text: debug ? `[${p.lines.join('\n')}]` : p.lines.join('\n')
+ })),
+ ]
+ );
+ }
+}
+
+class PositionOffsetTransformer {
+ private readonly lineStartOffsetByLineIdx: number[];
+
+ constructor(text: string) {
+ this.lineStartOffsetByLineIdx = [];
+ this.lineStartOffsetByLineIdx.push(0);
+ for (let i = 0; i < text.length; i++) {
+ if (text.charAt(i) === '\n') {
+ this.lineStartOffsetByLineIdx.push(i + 1);
+ }
+ }
+ }
+
+ getOffset(position: Position): number {
+ return this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1;
+ }
+}
+
+function applyEdits(text: string, edits: { range: IRange, text: string }[]): string {
+ const transformer = new PositionOffsetTransformer(text);
+ const offsetEdits = edits.map(e => {
+ const range = Range.lift(e.range);
+ return ({
+ startOffset: transformer.getOffset(range.getStartPosition()),
+ endOffset: transformer.getOffset(range.getEndPosition()),
+ text: e.text
+ });
+ });
+
+ offsetEdits.sort((a, b) => b.startOffset - a.startOffset);
+
+ for (const edit of offsetEdits) {
+ text = text.substring(0, edit.startOffset) + edit.text + text.substring(edit.endOffset);
+ }
+
+ return text;
}
export class GhostTextPart {
constructor(
readonly column: number,
- readonly lines: string[],
+ readonly lines: readonly string[],
) {
}
diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts
index 25ff2d76792..96e004f85be 100644
--- a/src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts
+++ b/src/vs/editor/contrib/inlineCompletions/ghostTextModel.ts
@@ -27,6 +27,9 @@ export abstract class DelegatingModel extends Disposable implements GhostTextWid
}
protected setTargetModel(model: GhostTextWidgetModel | undefined): void {
+ if (this.currentModelRef.value?.object === model) {
+ return;
+ }
this.currentModelRef.clear();
this.currentModelRef.value = model ? createDisposableRef(model, model.onDidChange(() => {
this.hasCachedGhostText = false;
diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts
index cff08300855..fb015f17e67 100644
--- a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts
+++ b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts
@@ -25,20 +25,21 @@ import { GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostT
import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations';
import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel';
+import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
const ttPolicy = window.trustedTypes?.createPolicy('editorGhostText', { createHTML: value => value });
export class GhostTextWidget extends Disposable {
private disposed = false;
- private readonly partsWidget = this._register(new DecorationsWidget(this.editor, this.codeEditorService, this.themeService));
+ private readonly partsWidget = this._register(this.instantiationService.createInstance(DecorationsWidget, this.editor));
private readonly additionalLinesWidget = this._register(new AdditionalLinesWidget(this.editor));
private viewMoreContentWidget: ViewMoreLinesContentWidget | undefined = undefined;
constructor(
private readonly editor: ICodeEditor,
private readonly model: GhostTextWidgetModel,
- @ICodeEditorService private readonly codeEditorService: ICodeEditorService,
- @IThemeService private readonly themeService: IThemeService,
+ @IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
@@ -86,7 +87,7 @@ export class GhostTextWidget extends Disposable {
const inlineTexts = new Array();
const additionalLines = new Array();
- function addToAdditionalLines(lines: string[], className: string | undefined) {
+ function addToAdditionalLines(lines: readonly string[], className: string | undefined) {
if (additionalLines.length > 0) {
const lastLine = additionalLines[additionalLines.length - 1];
if (className) {
@@ -94,7 +95,7 @@ export class GhostTextWidget extends Disposable {
}
lastLine.content += lines[0];
- lines.splice(0, 1);
+ lines = lines.slice(1);
}
for (const line of lines) {
additionalLines.push({
@@ -200,7 +201,8 @@ class DecorationsWidget implements IDisposable {
constructor(
private readonly editor: ICodeEditor,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
- @IThemeService private readonly themeService: IThemeService
+ @IThemeService private readonly themeService: IThemeService,
+ @IContextKeyService private readonly contextKeyService: IContextKeyService
) {
}
@@ -254,6 +256,9 @@ class DecorationsWidget implements IDisposable {
});
}
+ const key = this.contextKeyService.getContextKeyValue('config.editor.useInjectedText');
+ const shouldUseInjectedText = key === undefined ? true : !!key;
+
this.decorationIds = this.editor.deltaDecorations(this.decorationIds, parts.map(p => {
currentLinePrefix += line.substring(lastIndex, p.column - 1);
lastIndex = p.column - 1;
@@ -273,7 +278,10 @@ class DecorationsWidget implements IDisposable {
return ({
range: Range.fromPositions(new Position(lineNumber, p.column)),
- options: {
+ options: shouldUseInjectedText ? {
+ description: 'ghost-text',
+ after: { content: contentText, inlineClassName: 'ghost-text-decoration' }
+ } : {
...decorationType.resolve()
}
});
@@ -486,7 +494,7 @@ registerThemingParticipant((theme, collector) => {
const opacity = String(foreground.rgba.a);
const color = Color.Format.CSS.format(opaque(foreground))!;
- // We need to override the only used token type .mtk1
+ collector.addRule(`.monaco-editor .ghost-text-decoration { opacity: ${opacity}; color: ${color}; }`);
collector.addRule(`.monaco-editor .suggest-preview-text .ghost-text { opacity: ${opacity}; color: ${color}; }`);
}
diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts
index 4b91689814c..fbcc85b7557 100644
--- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts
+++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts
@@ -19,7 +19,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions';
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
-import { IDiffChange, stringDiff } from 'vs/base/common/diff/diff';
+import { IDiffChange, LcsDiff } from 'vs/base/common/diff/diff';
import { GhostTextWidgetModel, GhostText, BaseGhostTextWidgetModel, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/ghostText';
export class InlineCompletionsModel extends Disposable implements GhostTextWidgetModel {
@@ -540,12 +540,12 @@ export function inlineCompletionToGhostText(inlineCompletion: NormalizedInlineCo
return new GhostText(lineNumber, parts, 0);
}
-let lastRequest: { originalValue: string, newValue: string, changes: IDiffChange[] } | undefined = undefined;
+let lastRequest: { originalValue: string, newValue: string, changes: readonly IDiffChange[] } | undefined = undefined;
function cachingDiff(originalValue: string, newValue: string): readonly IDiffChange[] {
if (lastRequest?.originalValue === originalValue && lastRequest?.newValue === newValue) {
return lastRequest?.changes;
} else {
- const changes = stringDiff(originalValue, newValue, false);
+ const changes = smartDiff(originalValue, newValue);
lastRequest = {
originalValue,
newValue,
@@ -555,6 +555,63 @@ function cachingDiff(originalValue: string, newValue: string): readonly IDiffCha
}
}
+/**
+ * When matching `if ()` with `if (f() = 1) { g(); }`,
+ * align it like this: `if ( )`
+ * Not like this: `if ( )`
+ * Also not like this: `if ( )`.
+ *
+ * The parenthesis are preprocessed to ensure that they match correctly.
+ */
+function smartDiff(originalValue: string, newValue: string): readonly IDiffChange[] {
+ function getMaxCharCode(val: string): number {
+ let maxCharCode = 0;
+ for (let i = 0, len = val.length; i < len; i++) {
+ const charCode = val.charCodeAt(i);
+ if (charCode > maxCharCode) {
+ maxCharCode = charCode;
+ }
+ }
+ return maxCharCode;
+ }
+ const maxCharCode = Math.max(getMaxCharCode(originalValue), getMaxCharCode(newValue));
+ function getUniqueCharCode(id: number): number {
+ if (id < 0) {
+ throw new Error('unexpected');
+ }
+ return maxCharCode + id + 1;
+ }
+
+ function getElements(source: string): Int32Array {
+ let level = 0;
+ let group = 0;
+ const characters = new Int32Array(source.length);
+ for (let i = 0, len = source.length; i < len; i++) {
+ const id = group * 100 + level;
+
+ // TODO support more brackets
+ if (source[i] === '(') {
+ characters[i] = getUniqueCharCode(2 * id);
+ level++;
+ } else if (source[i] === ')') {
+ characters[i] = getUniqueCharCode(2 * id + 1);
+ if (level === 1) {
+ group++;
+ }
+ level = Math.max(level - 1, 0);
+ } else {
+ characters[i] = source.charCodeAt(i);
+ }
+ }
+ return characters;
+ }
+
+ const elements1 = getElements(originalValue);
+ const elements2 = getElements(newValue);
+
+ return new LcsDiff({ getElements: () => elements1 }, { getElements: () => elements2 }).ComputeDiff(false).changes;
+}
+
export interface LiveInlineCompletion extends NormalizedInlineCompletion {
sourceProvider: InlineCompletionsProvider;
sourceInlineCompletion: InlineCompletion;
diff --git a/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts
index 1febbae97e6..812aa6f1787 100644
--- a/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts
+++ b/src/vs/editor/contrib/inlineCompletions/test/inlineCompletionsProvider.test.ts
@@ -10,7 +10,7 @@ import { Range } from 'vs/editor/common/core/range';
import { InlineCompletionsProvider, InlineCompletionsProviderRegistry } from 'vs/editor/common/modes';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { InlineCompletionsModel, inlineCompletionToGhostText } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsModel';
-import { GhostTextContext, MockInlineCompletionsProvider, renderGhostTextToText } from 'vs/editor/contrib/inlineCompletions/test/utils';
+import { GhostTextContext, MockInlineCompletionsProvider } from 'vs/editor/contrib/inlineCompletions/test/utils';
import { ITestCodeEditor, TestCodeEditorCreationOptions, withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import sinon = require('sinon');
@@ -30,7 +30,7 @@ suite('Inline Completions', () => {
const options = ['prefix', 'subword'] as const;
const result = {} as any;
for (const option of options) {
- result[option] = renderGhostTextToText(inlineCompletionToGhostText({ text: suggestion, range }, tempModel, option), cleanedText);
+ result[option] = inlineCompletionToGhostText({ text: suggestion, range }, tempModel, option)?.render(cleanedText, true);
}
tempModel.dispose();
@@ -75,6 +75,12 @@ suite('Inline Completions', () => {
test('Multi Part Diffing', () => {
assert.deepStrictEqual(getOutput('foo[()]', '(x);'), { prefix: undefined, subword: 'foo([x])[;]' });
assert.deepStrictEqual(getOutput('[\tfoo]', '\t\tfoobar'), { prefix: undefined, subword: '\t[\t]foo[bar]' });
+ assert.deepStrictEqual(getOutput('[(y ===)]', '(y === 1) { f(); }'), { prefix: undefined, subword: '(y ===[ 1])[ { f(); }]' });
+ assert.deepStrictEqual(getOutput('[(y ==)]', '(y === 1) { f(); }'), { prefix: undefined, subword: '(y ==[= 1])[ { f(); }]' });
+ });
+
+ test('Multi Part Diffing 1', () => {
+ assert.deepStrictEqual(getOutput('[if () ()]', 'if (1 == f()) ()'), { prefix: undefined, subword: 'if ([1 == f()]) ()' });
});
});
diff --git a/src/vs/editor/contrib/inlineCompletions/test/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/utils.ts
index 72226659481..e1e6f61e723 100644
--- a/src/vs/editor/contrib/inlineCompletions/test/utils.ts
+++ b/src/vs/editor/contrib/inlineCompletions/test/utils.ts
@@ -10,27 +10,8 @@ import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
import { Position } from 'vs/editor/common/core/position';
import { ITextModel } from 'vs/editor/common/model';
import { InlineCompletionsProvider, InlineCompletion, InlineCompletionContext } from 'vs/editor/common/modes';
-import { GhostText, GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostText';
+import { GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostText';
import { ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
-import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
-
-export function renderGhostTextToText(ghostText: GhostText, text: string): string;
-export function renderGhostTextToText(ghostText: GhostText | undefined, text: string): string | undefined;
-export function renderGhostTextToText(ghostText: GhostText | undefined, text: string): string | undefined {
- if (!ghostText) {
- return undefined;
- }
- const l = ghostText.lineNumber;
- const tempModel = createTextModel(text);
- tempModel.applyEdits(
- [
- ...ghostText.parts.map(p => ({ range: { startLineNumber: l, endLineNumber: l, startColumn: p.column, endColumn: p.column }, text: `[${p.lines.join('\n')}]` })),
- ]
- );
- const value = tempModel.getValue();
- tempModel.dispose();
- return value;
-}
export class MockInlineCompletionsProvider implements InlineCompletionsProvider {
private returnValue: InlineCompletion[] = [];
@@ -110,7 +91,7 @@ export class GhostTextContext extends Disposable {
const ghostText = this.model?.ghostText;
let view: string | undefined;
if (ghostText) {
- view = renderGhostTextToText(ghostText, this.editor.getValue());
+ view = ghostText.render(this.editor.getValue(), true);
} else {
view = this.editor.getValue();
}
diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts
index 03abcd630f6..cf2c618c495 100644
--- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts
+++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts
@@ -408,6 +408,10 @@ suite('SplitLinesCollection', () => {
function assertAllMinimapLinesRenderingData(splitLinesCollection: SplitLinesCollection, all: ITestMinimapLineRenderingData[]): void {
let lineCount = all.length;
+ for (let line = 1; line <= lineCount; line++) {
+ assert.strictEqual(splitLinesCollection.getViewLineData(line).content, splitLinesCollection.getViewLineContent(line));
+ }
+
for (let start = 1; start <= lineCount; start++) {
for (let end = start; end <= lineCount; end++) {
let count = end - start + 1;
@@ -419,6 +423,7 @@ suite('SplitLinesCollection', () => {
expected[i] = (needed[i] ? all[start - 1 + i] : null);
}
let actual = splitLinesCollection.getViewLinesData(start, end, needed);
+
assertMinimapLinesRenderingData(actual, expected);
// Comment out next line to test all possible combinations
break;
diff --git a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts
index f0b0996b5b4..21d34ce6393 100644
--- a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts
+++ b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts
@@ -49,7 +49,7 @@ suite('ViewModelDecorations', () => {
// starts before viewport, ends after viewport
accessor.addDecoration(new Range(1, 2, 1, 51), createOpts('dec5'));
- // starts at viewport start, ends at viewport start
+ // starts at viewport start, ends at viewport start (will not be visible on view line 2)
accessor.addDecoration(new Range(1, 14, 1, 14), createOpts('dec6'));
// starts at viewport start, ends inside viewport
accessor.addDecoration(new Range(1, 14, 1, 16), createOpts('dec7'));
@@ -97,20 +97,41 @@ suite('ViewModelDecorations', () => {
'dec14',
]);
- let inlineDecorations1 = viewModel.getViewLineRenderingData(
+ const inlineDecorations1 = viewModel.getViewLineRenderingData(
+ new Range(1, viewModel.getLineMinColumn(1), 2, viewModel.getLineMaxColumn(2)),
+ 1
+ ).inlineDecorations;
+
+ // view line 1: (1,1 -> 1,14)
+ assert.deepStrictEqual(inlineDecorations1, [
+ new InlineDecoration(new Range(1, 2, 1, 3), 'i-dec1', InlineDecorationType.Regular),
+ new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec1', InlineDecorationType.Before),
+ new InlineDecoration(new Range(1, 3, 1, 3), 'a-dec1', InlineDecorationType.After),
+ new InlineDecoration(new Range(1, 2, 1, 14), 'i-dec2', InlineDecorationType.Regular),
+ new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec2', InlineDecorationType.Before),
+ new InlineDecoration(new Range(1, 14, 1, 14), 'a-dec2', InlineDecorationType.After),
+ new InlineDecoration(new Range(1, 2, 2, 2), 'i-dec3', InlineDecorationType.Regular),
+ new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec3', InlineDecorationType.Before),
+ new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular),
+ new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec4', InlineDecorationType.Before),
+ new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular),
+ new InlineDecoration(new Range(1, 2, 1, 2), 'b-dec5', InlineDecorationType.Before),
+ new InlineDecoration(new Range(1, 14, 1, 14), 'i-dec6', InlineDecorationType.Regular),
+ new InlineDecoration(new Range(1, 14, 1, 14), 'b-dec6', InlineDecorationType.Before),
+ new InlineDecoration(new Range(1, 14, 1, 14), 'a-dec6', InlineDecorationType.After),
+ ]);
+
+ const inlineDecorations2 = viewModel.getViewLineRenderingData(
new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)),
2
).inlineDecorations;
// view line 2: (1,14 -> 1,24)
- assert.deepStrictEqual(inlineDecorations1, [
+ assert.deepStrictEqual(inlineDecorations2, [
new InlineDecoration(new Range(1, 2, 2, 2), 'i-dec3', InlineDecorationType.Regular),
new InlineDecoration(new Range(2, 2, 2, 2), 'a-dec3', InlineDecorationType.After),
new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular),
new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular),
- new InlineDecoration(new Range(2, 1, 2, 1), 'i-dec6', InlineDecorationType.Regular),
- new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec6', InlineDecorationType.Before),
- new InlineDecoration(new Range(2, 1, 2, 1), 'a-dec6', InlineDecorationType.After),
new InlineDecoration(new Range(2, 1, 2, 3), 'i-dec7', InlineDecorationType.Regular),
new InlineDecoration(new Range(2, 1, 2, 1), 'b-dec7', InlineDecorationType.Before),
new InlineDecoration(new Range(2, 3, 2, 3), 'a-dec7', InlineDecorationType.After),
@@ -127,13 +148,13 @@ suite('ViewModelDecorations', () => {
new InlineDecoration(new Range(2, 3, 2, 3), 'b-dec12', InlineDecorationType.Before),
]);
- let inlineDecorations2 = viewModel.getViewLineRenderingData(
+ const inlineDecorations3 = viewModel.getViewLineRenderingData(
new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3)),
3
).inlineDecorations;
// view line 3 (24 -> 36)
- assert.deepStrictEqual(inlineDecorations2, [
+ assert.deepStrictEqual(inlineDecorations3, [
new InlineDecoration(new Range(1, 2, 3, 13), 'i-dec4', InlineDecorationType.Regular),
new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec4', InlineDecorationType.After),
new InlineDecoration(new Range(1, 2, 5, 8), 'i-dec5', InlineDecorationType.Regular),
@@ -143,6 +164,9 @@ suite('ViewModelDecorations', () => {
new InlineDecoration(new Range(2, 3, 3, 13), 'i-dec11', InlineDecorationType.Regular),
new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec11', InlineDecorationType.After),
new InlineDecoration(new Range(2, 3, 5, 8), 'i-dec12', InlineDecorationType.Regular),
+ new InlineDecoration(new Range(3, 13, 3, 13), 'i-dec13', InlineDecorationType.Regular),
+ new InlineDecoration(new Range(3, 13, 3, 13), 'b-dec13', InlineDecorationType.Before),
+ new InlineDecoration(new Range(3, 13, 3, 13), 'a-dec13', InlineDecorationType.After),
]);
});
});
diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts
index 89b6a013a92..8fe3760dc2c 100644
--- a/src/vs/monaco.d.ts
+++ b/src/vs/monaco.d.ts
@@ -1465,6 +1465,10 @@ declare namespace monaco.editor {
* If set, the decoration will be rendered inline with the text with this CSS class name.
*/
readonly inlineClassName?: string | null;
+ /**
+ * If there is an `inlineClassName` which affects letter spacing.
+ */
+ readonly inlineClassNameAffectsLetterSpacing?: boolean;
}
/**
diff --git a/src/vs/platform/environment/electron-main/environmentMainService.ts b/src/vs/platform/environment/electron-main/environmentMainService.ts
index 27c94de78d6..d95965be863 100644
--- a/src/vs/platform/environment/electron-main/environmentMainService.ts
+++ b/src/vs/platform/environment/electron-main/environmentMainService.ts
@@ -30,6 +30,7 @@ export interface IEnvironmentMainService extends INativeEnvironmentService {
// --- IPC
mainIPCHandle: string;
+ mainLockfile: string;
// --- config
sandbox: boolean;
@@ -52,6 +53,9 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
@memoize
get mainIPCHandle(): string { return createStaticIPCHandle(this.userDataPath, 'main', this.productService.version); }
+ @memoize
+ get mainLockfile(): string { return join(this.userDataPath, 'code.lock'); }
+
@memoize
get sandbox(): boolean { return !!this.args['__sandbox']; }
diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
new file mode 100644
index 00000000000..083e7c2cf0b
--- /dev/null
+++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
@@ -0,0 +1,646 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as nls from 'vs/nls';
+import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
+import {
+ IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
+ IGalleryExtension,
+ InstallExtensionEvent, DidUninstallExtensionEvent,
+ IExtensionIdentifier,
+ IReportedExtension,
+ InstallOperation,
+ INSTALL_ERROR_MALICIOUS,
+ INSTALL_ERROR_INCOMPATIBLE,
+ ExtensionManagementError,
+ InstallOptions,
+ InstallVSIXOptions,
+ InstallExtensionResult,
+ UninstallOptions,
+ IGalleryMetadata,
+ StatisticType
+} from 'vs/platform/extensionManagement/common/extensionManagement';
+import { areSameExtensions, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, ExtensionIdentifierWithVersion, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
+import { Event, Emitter } from 'vs/base/common/event';
+import product from 'vs/platform/product/common/product';
+import { ILogService } from 'vs/platform/log/common/log';
+import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
+import { CancellationToken } from 'vs/base/common/cancellation';
+import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
+import { canceled, getErrorMessage } from 'vs/base/common/errors';
+import { URI } from 'vs/base/common/uri';
+import { Barrier, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
+
+export const INSTALL_ERROR_VALIDATING = 'validating';
+export const ERROR_UNKNOWN = 'unknown';
+export const INSTALL_ERROR_LOCAL = 'local';
+
+export interface IInstallExtensionTask {
+ readonly identifier: IExtensionIdentifier;
+ readonly source: IGalleryExtension | URI;
+ readonly operation: InstallOperation;
+ run(): Promise;
+ waitUntilTaskIsFinished(): Promise;
+ cancel(): void;
+}
+
+export type UninstallExtensionTaskOptions = { readonly remove?: boolean; readonly versionOnly?: boolean };
+
+export interface IUninstallExtensionTask {
+ readonly extension: ILocalExtension;
+ run(): Promise;
+ waitUntilTaskIsFinished(): Promise;
+ cancel(): void;
+}
+
+export abstract class AbstractExtensionManagementService extends Disposable implements IExtensionManagementService {
+
+ declare readonly _serviceBrand: undefined;
+
+ private reportedExtensions: Promise | undefined;
+ private lastReportTimestamp = 0;
+ private readonly installingExtensions = new Map();
+ private readonly uninstallingExtensions = new Map();
+
+ private readonly _onInstallExtension = this._register(new Emitter());
+ readonly onInstallExtension: Event = this._onInstallExtension.event;
+
+ protected readonly _onDidInstallExtensions = this._register(new Emitter());
+ readonly onDidInstallExtensions = this._onDidInstallExtensions.event;
+
+ protected readonly _onUninstallExtension = this._register(new Emitter());
+ readonly onUninstallExtension: Event = this._onUninstallExtension.event;
+
+ protected _onDidUninstallExtension = this._register(new Emitter());
+ onDidUninstallExtension: Event = this._onDidUninstallExtension.event;
+
+ constructor(
+ @IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService,
+ @ITelemetryService protected readonly telemetryService: ITelemetryService,
+ @ILogService protected readonly logService: ILogService,
+ ) {
+ super();
+ this._register(toDisposable(() => {
+ this.installingExtensions.forEach(task => task.cancel());
+ this.uninstallingExtensions.forEach(promise => promise.cancel());
+ this.installingExtensions.clear();
+ this.uninstallingExtensions.clear();
+ }));
+ }
+
+ async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise {
+ if (!this.galleryService.isEnabled()) {
+ throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
+ }
+
+ try {
+ extension = await this.checkAndGetCompatibleVersion(extension);
+ } catch (error) {
+ this.logService.error(getErrorMessage(error));
+ reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
+ throw error;
+ }
+
+ if (!await this.canInstall(extension)) {
+ const error = new ExtensionManagementError(`Not supported`, INSTALL_ERROR_VALIDATING);
+ this.logService.error(`Canno install extension as it is not supported.`, extension.identifier.id, error.message);
+ reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
+ throw error;
+ }
+
+ const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
+ if (manifest === null) {
+ const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, INSTALL_ERROR_VALIDATING);
+ this.logService.error(`Failed to install extension:`, extension.identifier.id, error.message);
+ reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
+ throw error;
+ }
+
+ return this.installExtension(manifest, extension, options);
+ }
+
+ async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise {
+ this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
+ return this.unininstallExtension(extension, options);
+ }
+
+ async reinstallFromGallery(extension: ILocalExtension): Promise {
+ this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id);
+ if (!this.galleryService.isEnabled()) {
+ throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
+ }
+
+ const galleryExtension = await this.findGalleryExtension(extension);
+ if (!galleryExtension) {
+ throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"));
+ }
+
+ await this.createUninstallExtensionTask(extension, { remove: true, versionOnly: true }).run();
+ await this.installFromGallery(galleryExtension);
+ }
+
+ getExtensionsReport(): Promise {
+ const now = new Date().getTime();
+
+ if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
+ this.reportedExtensions = this.updateReportCache();
+ this.lastReportTimestamp = now;
+ }
+
+ return this.reportedExtensions;
+ }
+
+ protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise {
+ // only cache gallery extensions tasks
+ if (!URI.isUri(extension)) {
+ let installExtensionTask = this.installingExtensions.get(new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key());
+ if (installExtensionTask) {
+ this.logService.info('Extensions is already requested to install', extension.identifier.id);
+ return installExtensionTask.waitUntilTaskIsFinished();
+ }
+ options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ };
+ }
+
+ const allInstallExtensionTasks: { task: IInstallExtensionTask, manifest: IExtensionManifest }[] = [];
+ const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = [];
+ const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options);
+ if (!URI.isUri(extension)) {
+ this.installingExtensions.set(new ExtensionIdentifierWithVersion(installExtensionTask.identifier, manifest.version).key(), installExtensionTask);
+ }
+ this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension });
+ this.logService.info('Installing extension:', installExtensionTask.identifier.id);
+ allInstallExtensionTasks.push({ task: installExtensionTask, manifest });
+ let installExtensionHasDependents: boolean = false;
+
+ try {
+ if (options.donotIncludePackAndDependencies) {
+ this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id);
+ } else {
+ try {
+ const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack);
+ for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) {
+ installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier));
+ if (this.installingExtensions.has(new ExtensionIdentifierWithVersion(gallery.identifier, gallery.version).key())) {
+ this.logService.info('Extension is already requested to install', gallery.identifier.id);
+ } else {
+ const task = this.createInstallExtensionTask(manifest, gallery, { ...options, donotIncludePackAndDependencies: true });
+ this.installingExtensions.set(new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key(), task);
+ this._onInstallExtension.fire({ identifier: task.identifier, source: gallery });
+ this.logService.info('Installing extension:', task.identifier.id);
+ allInstallExtensionTasks.push({ task, manifest });
+ }
+ }
+ } catch (error) {
+ this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id);
+ this.logService.error(error);
+ throw error;
+ }
+ }
+
+ const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => {
+ result.set(task.identifier.id.toLowerCase(), { task, manifest });
+ return result;
+ }, new Map());
+
+ while (extensionsToInstallMap.size) {
+ let extensionsToInstall;
+ const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase())));
+ if (extensionsWithoutDepsToInstall.length) {
+ extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall
+ /* If the main extension has no dependents remove it and install it at the end */
+ : extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents));
+ } else {
+ this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id));
+ extensionsToInstall = [...extensionsToInstallMap.values()];
+ }
+
+ // Install extensions in parallel and wait until all extensions are installed / failed
+ const result = await Promise.allSettled(extensionsToInstall.map(async ({ task }) => {
+ const startTime = new Date().getTime();
+ try {
+ const local = await task.run();
+ if (!URI.isUri(task.source)) {
+ reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(task.source), new Date().getTime() - startTime, undefined);
+ }
+ installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source });
+ } catch (error) {
+ if (!URI.isUri(task.source)) {
+ reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(task.source), new Date().getTime() - startTime, error);
+ }
+ this.logService.error('Error while installing the extension:', task.identifier.id);
+ this.logService.error(error);
+ throw error;
+ } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); }
+ }));
+
+ // Collect the errors
+ const errors = result.reduce((errors, r) => { if (r.status === 'rejected') { errors.push(r.reason); } return errors; }, []);
+ // If there are errors, throw the error.
+ if (errors.length) { throw joinErrors(errors); }
+ }
+
+ installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id));
+ this._onDidInstallExtensions.fire(installResults);
+ return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local;
+
+ } catch (error) {
+
+ // cancel all tasks
+ allInstallExtensionTasks.forEach(({ task }) => task.cancel());
+
+ // rollback installed extensions
+ if (installResults.length) {
+ try {
+ const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true }).run()));
+ for (let index = 0; index < result.length; index++) {
+ const r = result[index];
+ const { identifier } = installResults[index];
+ if (r.status === 'fulfilled') {
+ this.logService.info('Rollback: Uninstalled extension', identifier.id);
+ } else {
+ this.logService.warn('Rollback: Error while uninstalling extension', identifier.id, getErrorMessage(r.reason));
+ }
+ }
+ } catch (error) {
+ // ignore error
+ this.logService.warn('Error while rolling back extensions', getErrorMessage(error), installResults.map(({ identifier }) => identifier.id));
+ }
+ }
+
+ this.logService.error(`Failed to install extension:`, installExtensionTask.identifier.id, getErrorMessage(error));
+ this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source })));
+
+ if (error instanceof Error) {
+ error.name = error && (error).code ? (error).code : ERROR_UNKNOWN;
+ }
+ throw error;
+ } finally {
+ /* Remove the gallery tasks from the cache */
+ for (const { task, manifest } of allInstallExtensionTasks) {
+ if (!URI.isUri(task.source)) {
+ const key = new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key();
+ if (!this.installingExtensions.delete(key)) {
+ this.logService.warn('Installation task is not found in the cache', key);
+ }
+ }
+ }
+ }
+ }
+
+ private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean): Promise<{ gallery: IGalleryExtension, manifest: IExtensionManifest }[]> {
+ if (!this.galleryService.isEnabled()) {
+ return [];
+ }
+
+ let installed = await this.getInstalled();
+ const knownIdentifiers = [extensionIdentifier, ...(installed).map(i => i.identifier)];
+
+ const allDependenciesAndPacks: { gallery: IGalleryExtension, manifest: IExtensionManifest }[] = [];
+ const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise => {
+ const dependenciesAndPackExtensions: string[] = manifest.extensionDependencies || [];
+ if (manifest.extensionPack) {
+ const existing = getOnlyNewlyAddedFromExtensionPack ? installed.find(e => areSameExtensions(e.identifier, extensionIdentifier)) : undefined;
+ for (const extension of manifest.extensionPack) {
+ // add only those extensions which are new in currently installed extension
+ if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) {
+ if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) {
+ dependenciesAndPackExtensions.push(extension);
+ }
+ }
+ }
+ }
+
+ if (dependenciesAndPackExtensions.length) {
+ // filter out installed and known extensions
+ const identifiers = [...knownIdentifiers, ...allDependenciesAndPacks.map(r => r.gallery.identifier)];
+ const names = dependenciesAndPackExtensions.filter(id => identifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id })));
+ if (names.length) {
+ const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None);
+ for (const galleryExtension of galleryResult.firstPage) {
+ if (identifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {
+ continue;
+ }
+ const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension);
+ if (!await this.canInstall(compatibleExtension)) {
+ this.logService.info('Skipping the extension as it cannot be installed', compatibleExtension.identifier.id);
+ continue;
+ }
+ const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None);
+ if (manifest === null) {
+ throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, INSTALL_ERROR_VALIDATING);
+ }
+ allDependenciesAndPacks.push({ gallery: compatibleExtension, manifest });
+ await collectDependenciesAndPackExtensionsToInstall(compatibleExtension.identifier, manifest);
+ }
+ }
+ }
+ };
+
+ await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest);
+ installed = await this.getInstalled();
+ return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier)));
+ }
+
+ private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise {
+ if (await this.isMalicious(extension)) {
+ throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), INSTALL_ERROR_MALICIOUS);
+ }
+
+ const compatibleExtension = await this.galleryService.getCompatibleExtension(extension);
+ if (!compatibleExtension) {
+ throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE);
+ }
+
+ return compatibleExtension;
+ }
+
+ private async isMalicious(extension: IGalleryExtension): Promise {
+ const report = await this.getExtensionsReport();
+ return getMaliciousExtensionsSet(report).has(extension.identifier.id);
+ }
+
+ private async unininstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise {
+ const uninstallExtensionTask = this.uninstallingExtensions.get(extension.identifier.id.toLowerCase());
+ if (uninstallExtensionTask) {
+ this.logService.info('Extensions is already requested to uninstall', extension.identifier.id);
+ return uninstallExtensionTask.waitUntilTaskIsFinished();
+ }
+
+ const createUninstallExtensionTask = (extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask => {
+ const uninstallExtensionTask = this.createUninstallExtensionTask(extension, options);
+ this.uninstallingExtensions.set(uninstallExtensionTask.extension.identifier.id.toLowerCase(), uninstallExtensionTask);
+ this.logService.info('Uninstalling extension:', extension.identifier.id);
+ this._onUninstallExtension.fire(extension.identifier);
+ return uninstallExtensionTask;
+ };
+
+ const postUninstallExtension = (extension: ILocalExtension, error?: ExtensionManagementError): void => {
+ if (error) {
+ this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
+ } else {
+ this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
+ }
+ reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error);
+ this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code });
+ };
+
+ const allTasks: IUninstallExtensionTask[] = [];
+ const processedTasks: IUninstallExtensionTask[] = [];
+
+ try {
+ allTasks.push(createUninstallExtensionTask(extension, {}));
+ const installed = await this.getInstalled(ExtensionType.User);
+
+ if (options.donotIncludePack) {
+ this.logService.info('Uninstalling the extension without including packed extension', extension.identifier.id);
+ } else {
+ const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
+ for (const packedExtension of packedExtensions) {
+ if (this.uninstallingExtensions.has(packedExtension.identifier.id.toLowerCase())) {
+ this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id);
+ } else {
+ allTasks.push(createUninstallExtensionTask(packedExtension, {}));
+ }
+ }
+ }
+
+ if (options.donotCheckDependents) {
+ this.logService.info('Uninstalling the extension without checking dependents', extension.identifier.id);
+ } else {
+ this.checkForDependents(allTasks.map(task => task.extension), installed, extension);
+ }
+
+ // Uninstall extensions in parallel and wait until all extensions are uninstalled / failed
+ const result = await Promise.allSettled(allTasks.map(async task => {
+ try {
+ await task.run();
+ // only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
+ if (task.extension.identifier.uuid) {
+ try {
+ await this.galleryService.reportStatistic(task.extension.manifest.publisher, task.extension.manifest.name, task.extension.manifest.version, StatisticType.Uninstall);
+ } catch (error) { /* ignore */ }
+ }
+ postUninstallExtension(task.extension);
+ } catch (e) {
+ const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN);
+ postUninstallExtension(task.extension, error);
+ throw error;
+ } finally {
+ processedTasks.push(task);
+ }
+ }));
+
+ // Collect the errors
+ const errors = result.reduce((errors, r) => { if (r.status === 'rejected') { errors.push(r.reason); } return errors; }, []);
+ // If there are errors, throw the error.
+ if (errors.length) { throw joinErrors(errors); }
+
+ } catch (e) {
+ const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN);
+ for (const task of allTasks) {
+ // cancel the tasks
+ try { task.cancel(); } catch (error) { /* ignore */ }
+ if (!processedTasks.includes(task)) {
+ postUninstallExtension(task.extension, error);
+ }
+ }
+ throw error;
+ } finally {
+ // Remove tasks from cache
+ for (const task of allTasks) {
+ if (!this.uninstallingExtensions.delete(task.extension.identifier.id.toLowerCase())) {
+ this.logService.warn('Uninstallation task is not found in the cache', task.extension.identifier.id);
+ }
+ }
+ }
+ }
+
+ private checkForDependents(extensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], extensionToUninstall: ILocalExtension): void {
+ for (const extension of extensionsToUninstall) {
+ const dependents = this.getDependents(extension, installed);
+ if (dependents.length) {
+ const remainingDependents = dependents.filter(dependent => extensionsToUninstall.indexOf(dependent) === -1);
+ if (remainingDependents.length) {
+ throw new Error(this.getDependentsErrorMessage(extension, remainingDependents, extensionToUninstall));
+ }
+ }
+ }
+ }
+
+ private getDependentsErrorMessage(dependingExtension: ILocalExtension, dependents: ILocalExtension[], extensionToUninstall: ILocalExtension): string {
+ if (extensionToUninstall === dependingExtension) {
+ if (dependents.length === 1) {
+ return nls.localize('singleDependentError', "Cannot uninstall '{0}' extension. '{1}' extension depends on this.",
+ extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
+ }
+ if (dependents.length === 2) {
+ return nls.localize('twoDependentsError', "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.",
+ extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
+ }
+ return nls.localize('multipleDependentsError', "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.",
+ extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
+ }
+ if (dependents.length === 1) {
+ return nls.localize('singleIndirectDependentError', "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.",
+ extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
+ || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
+ }
+ if (dependents.length === 2) {
+ return nls.localize('twoIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.",
+ extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
+ || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
+ }
+ return nls.localize('multipleIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.",
+ extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
+ || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
+
+ }
+
+ private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
+ if (checked.indexOf(extension) !== -1) {
+ return [];
+ }
+ checked.push(extension);
+ const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : [];
+ if (extensionsPack.length) {
+ const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier)));
+ const packOfPackedExtensions: ILocalExtension[] = [];
+ for (const packedExtension of packedExtensions) {
+ packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
+ }
+ return [...packedExtensions, ...packOfPackedExtensions];
+ }
+ return [];
+ }
+
+ private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] {
+ return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
+ }
+
+ private async findGalleryExtension(local: ILocalExtension): Promise {
+ if (local.identifier.uuid) {
+ const galleryExtension = await this.findGalleryExtensionById(local.identifier.uuid);
+ return galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id);
+ }
+ return this.findGalleryExtensionByName(local.identifier.id);
+ }
+
+ private async findGalleryExtensionById(uuid: string): Promise {
+ const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None);
+ return galleryResult.firstPage[0];
+ }
+
+ private async findGalleryExtensionByName(name: string): Promise {
+ const galleryResult = await this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None);
+ return galleryResult.firstPage[0];
+ }
+
+ private async updateReportCache(): Promise {
+ try {
+ this.logService.trace('ExtensionManagementService.refreshReportedCache');
+ const result = await this.galleryService.getExtensionsReport();
+ this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
+ return result;
+ } catch (err) {
+ this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
+ return [];
+ }
+ }
+
+ abstract zip(extension: ILocalExtension): Promise;
+ abstract unzip(zipLocation: URI): Promise;
+ abstract getManifest(vsix: URI): Promise;
+ abstract install(vsix: URI, options?: InstallVSIXOptions): Promise;
+ abstract canInstall(extension: IGalleryExtension): Promise;
+ abstract getInstalled(type?: ExtensionType): Promise;
+
+ abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise;
+ abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise;
+
+ protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): IInstallExtensionTask;
+ protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask;
+}
+
+export function joinErrors(errorOrErrors: (Error | string) | (Array)): Error {
+ const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
+ if (errors.length === 1) {
+ return errors[0] instanceof Error ? errors[0] : new Error(errors[0]);
+ }
+ return errors.reduce((previousValue: Error, currentValue: Error | string) => {
+ return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
+ }, new Error(''));
+}
+
+export function reportTelemetry(telemetryService: ITelemetryService, eventName: string, extensionData: any, duration?: number, error?: Error): void {
+ const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined;
+ /* __GDPR__
+ "extensionGallery:install" : {
+ "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
+ "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
+ "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
+ "recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
+ "${include}": [
+ "${GalleryExtensionTelemetryData}"
+ ]
+ }
+ */
+ /* __GDPR__
+ "extensionGallery:uninstall" : {
+ "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
+ "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
+ "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
+ "${include}": [
+ "${GalleryExtensionTelemetryData}"
+ ]
+ }
+ */
+ /* __GDPR__
+ "extensionGallery:update" : {
+ "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
+ "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
+ "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
+ "${include}": [
+ "${GalleryExtensionTelemetryData}"
+ ]
+ }
+ */
+ telemetryService.publicLogError(eventName, { ...extensionData, success: !error, duration, errorcode });
+}
+
+export abstract class AbstractExtensionTask {
+
+ private readonly barrier = new Barrier();
+ private cancellablePromise: CancelablePromise | undefined;
+
+ async waitUntilTaskIsFinished(): Promise {
+ await this.barrier.wait();
+ return this.cancellablePromise!;
+ }
+
+ async run(): Promise {
+ if (!this.cancellablePromise) {
+ this.cancellablePromise = createCancelablePromise(token => this.doRun(token));
+ }
+ this.barrier.open();
+ return this.cancellablePromise;
+ }
+
+ cancel(): void {
+ if (!this.cancellablePromise) {
+ this.cancellablePromise = createCancelablePromise(token => {
+ return new Promise((c, e) => {
+ const disposable = token.onCancellationRequested(() => {
+ disposable.dispose();
+ e(canceled());
+ });
+ });
+ });
+ this.barrier.open();
+ }
+ this.cancellablePromise.cancel();
+ }
+
+ protected abstract doRun(token: CancellationToken): Promise;
+}
diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts
index d7dd6e2403f..7cf697e0d45 100644
--- a/src/vs/platform/extensionManagement/common/extensionManagement.ts
+++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts
@@ -174,13 +174,13 @@ export interface IExtensionGalleryService {
export interface InstallExtensionEvent {
identifier: IExtensionIdentifier;
- source: string | IGalleryExtension;
+ source: URI | IGalleryExtension;
}
export interface InstallExtensionResult {
readonly identifier: IExtensionIdentifier;
readonly operation: InstallOperation;
- readonly source?: string | IGalleryExtension;
+ readonly source?: URI | IGalleryExtension;
readonly local?: ILocalExtension;
}
diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts
index 427c86c9bd4..8c7587e22ad 100644
--- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts
+++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts
@@ -98,12 +98,20 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
private readonly channel: IChannel,
) {
super();
- this._register(this.channel.listen('onInstallExtension')(e => this._onInstallExtension.fire(e)));
- this._register(this.channel.listen('onDidInstallExtensions')(results => this._onDidInstallExtensions.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local })))));
+ this._register(this.channel.listen('onInstallExtension')(e => this._onInstallExtension.fire({ identifier: e.identifier, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source })));
+ this._register(this.channel.listen('onDidInstallExtensions')(results => this._onDidInstallExtensions.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source })))));
this._register(this.channel.listen('onUninstallExtension')(e => this._onUninstallExtension.fire(e)));
this._register(this.channel.listen('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire(e)));
}
+ private isUriComponents(thing: unknown): thing is UriComponents {
+ if (!thing) {
+ return false;
+ }
+ return typeof (thing).path === 'string' &&
+ typeof (thing).scheme === 'string';
+ }
+
zip(extension: ILocalExtension): Promise {
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(result)));
}
diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
index 3edb7536719..a87d5fdafe6 100644
--- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
@@ -6,28 +6,18 @@
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import * as pfs from 'vs/base/node/pfs';
-import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { zip, IFile } from 'vs/base/node/zip';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
IGalleryExtension, IGalleryMetadata,
- InstallExtensionEvent, DidUninstallExtensionEvent,
- StatisticType,
IExtensionIdentifier,
- IReportedExtension,
InstallOperation,
- INSTALL_ERROR_MALICIOUS,
- INSTALL_ERROR_INCOMPATIBLE,
ExtensionManagementError,
InstallOptions,
- UninstallOptions,
InstallVSIXOptions,
- InstallExtensionResult
} from 'vs/platform/extensionManagement/common/extensionManagement';
-import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
+import { areSameExtensions, getGalleryExtensionId, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
-import { createCancelablePromise, CancelablePromise, Promises, Barrier } from 'vs/base/common/async';
-import { Event, Emitter } from 'vs/base/common/event';
import * as semver from 'vs/base/common/semver/semver';
import { URI } from 'vs/base/common/uri';
import product from 'vs/platform/product/common/product';
@@ -50,14 +40,10 @@ import { ExtensionsScanner, ILocalExtensionManifest, IMetadata } from 'vs/platfo
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
import { ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensionsWatcher';
import { IFileService } from 'vs/platform/files/common/files';
-import { canceled, getErrorMessage } from 'vs/base/common/errors';
-import { isString } from 'vs/base/common/types';
+import { AbstractExtensionManagementService, joinErrors, IUninstallExtensionTask, IInstallExtensionTask, INSTALL_ERROR_VALIDATING, UninstallExtensionTaskOptions, AbstractExtensionTask } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled';
const INSTALL_ERROR_DOWNLOADING = 'downloading';
-const INSTALL_ERROR_VALIDATING = 'validating';
-const INSTALL_ERROR_LOCAL = 'local';
-const ERROR_UNKNOWN = 'unknown';
interface InstallableExtension {
zipPath: string;
@@ -65,49 +51,22 @@ interface InstallableExtension {
metadata?: IMetadata;
}
-interface InstallExtensionTask {
- readonly identifier: IExtensionIdentifier;
- readonly source: IGalleryExtension | string;
- readonly operation: InstallOperation;
- run(): Promise;
- waitUntilTaskIsFinished(): Promise;
- cancel(): void;
-}
-
-export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
-
- declare readonly _serviceBrand: undefined;
+export class ExtensionManagementService extends AbstractExtensionManagementService implements IExtensionManagementService {
private readonly extensionsScanner: ExtensionsScanner;
- private reportedExtensions: Promise | undefined;
- private lastReportTimestamp = 0;
- private readonly installingExtensions = new Map();
- private readonly uninstallingExtensions: Map> = new Map>();
private readonly manifestCache: ExtensionsManifestCache;
private readonly extensionsDownloader: ExtensionsDownloader;
- private readonly _onInstallExtension = this._register(new Emitter());
- readonly onInstallExtension: Event = this._onInstallExtension.event;
-
- private readonly _onDidInstallExtensions = this._register(new Emitter());
- readonly onDidInstallExtensions = this._onDidInstallExtensions.event;
-
- private readonly _onUninstallExtension = this._register(new Emitter());
- readonly onUninstallExtension: Event = this._onUninstallExtension.event;
-
- private _onDidUninstallExtension = this._register(new Emitter());
- onDidUninstallExtension: Event = this._onDidUninstallExtension.event;
-
constructor(
+ @IExtensionGalleryService galleryService: IExtensionGalleryService,
+ @ITelemetryService telemetryService: ITelemetryService,
+ @ILogService logService: ILogService,
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
- @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
- @ILogService private readonly logService: ILogService,
@optional(IDownloadService) private downloadService: IDownloadService,
- @ITelemetryService private readonly telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IFileService fileService: IFileService,
) {
- super();
+ super(galleryService, telemetryService, logService);
const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle));
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension)));
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
@@ -120,13 +79,6 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
removed.forEach(extension => this._onDidUninstallExtension.fire({ identifier: extension }));
}));
-
- this._register(toDisposable(() => {
- this.installingExtensions.forEach(task => task.cancel());
- this.uninstallingExtensions.forEach(promise => promise.cancel());
- this.installingExtensions.clear();
- this.uninstallingExtensions.clear();
- }));
}
async zip(extension: ILocalExtension): Promise {
@@ -148,6 +100,65 @@ export class ExtensionManagementService extends Disposable implements IExtension
return getManifest(zipPath);
}
+ getInstalled(type: ExtensionType | null = null): Promise {
+ return this.extensionsScanner.scanExtensions(type);
+ }
+
+ async canInstall(extension: IGalleryExtension): Promise {
+ return true;
+ }
+
+ async install(vsix: URI, options: InstallVSIXOptions = {}): Promise {
+ this.logService.trace('ExtensionManagementService#install', vsix.toString());
+
+ const downloadLocation = await this.downloadVsix(vsix);
+ const manifest = await getManifest(path.resolve(downloadLocation.fsPath));
+ if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version, product.date)) {
+ throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", getGalleryExtensionId(manifest.publisher, manifest.name), product.version));
+ }
+
+ return this.installExtension(manifest, downloadLocation, options);
+ }
+
+ async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise {
+ this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id);
+ local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), ...metadata });
+ this.manifestCache.invalidate();
+ return local;
+ }
+
+ async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise {
+ this.logService.trace('ExtensionManagementService#updateExtensionScope', local.identifier.id);
+ local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), isMachineScoped });
+ this.manifestCache.invalidate();
+ return local;
+ }
+
+ removeDeprecatedExtensions(): Promise {
+ return this.extensionsScanner.cleanUp();
+ }
+
+ private async downloadVsix(vsix: URI): Promise {
+ if (vsix.scheme === Schemas.file) {
+ return vsix;
+ }
+ if (!this.downloadService) {
+ throw new Error('Download service is not available');
+ }
+
+ const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid());
+ await this.downloadService.download(vsix, downloadedLocation);
+ return downloadedLocation;
+ }
+
+ protected createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): IInstallExtensionTask {
+ return URI.isUri(extension) ? new InstallVSIXTask(manifest, extension, options, this.galleryService, this.extensionsScanner, this.logService) : new InstallGalleryExtensionTask(extension, options, this.extensionsDownloader, this.extensionsScanner, this.logService);
+ }
+
+ protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask {
+ return new UninstallExtensionTask(extension, options, this.extensionsScanner);
+ }
+
private async collectFiles(extension: ILocalExtension): Promise {
const collectFilesFromDirectory = async (dir: string): Promise => {
@@ -173,588 +184,20 @@ export class ExtensionManagementService extends Disposable implements IExtension
return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f }));
}
- async canInstall(extension: IGalleryExtension): Promise {
- return true;
- }
-
- async install(vsix: URI, options: InstallVSIXOptions = {}): Promise {
- this.logService.trace('ExtensionManagementService#install', vsix.toString());
-
- const downloadLocation = await this.downloadVsix(vsix);
- const zipPath = path.resolve(downloadLocation.fsPath);
- const manifest = await getManifest(zipPath);
- if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version, product.date)) {
- throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", getGalleryExtensionId(manifest.publisher, manifest.name), product.version));
- }
-
- return this.installExtension(manifest, zipPath, options);
- }
-
- async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise {
- if (!this.galleryService.isEnabled()) {
- throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
- }
-
- try {
- extension = await this.checkAndGetCompatibleVersion(extension);
- } catch (error) {
- this.logService.error(getErrorMessage(error));
- reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
- throw error;
- }
-
- const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
- if (manifest === null) {
- const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, INSTALL_ERROR_VALIDATING);
- this.logService.error(`Failed to install extension:`, extension.identifier.id, error.message);
- reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
- throw error;
- }
-
- return this.installExtension(manifest, extension, options);
- }
-
- private async downloadVsix(vsix: URI): Promise {
- if (vsix.scheme === Schemas.file) {
- return vsix;
- }
- if (!this.downloadService) {
- throw new Error('Download service is not available');
- }
-
- const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid());
- await this.downloadService.download(vsix, downloadedLocation);
- return downloadedLocation;
- }
-
- private createInstallVSIXExtensionTask(manifest: IExtensionManifest, zipPath: string, options: InstallVSIXOptions): InstallVSIXTask {
- return new InstallVSIXTask(manifest, zipPath, options, this.galleryService, this.extensionsScanner, this.logService);
- }
-
- private createInstallFromGalleryExtensionTask(extension: IGalleryExtension, options: InstallOptions): InstallExtensionTask {
- return new InstallGalleryExtensionTask(extension, options, this.extensionsDownloader, this.telemetryService, this.extensionsScanner, this.logService);
- }
-
- private createInstallExtensionTask(manifest: IExtensionManifest, extension: string | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): InstallExtensionTask {
- return isString(extension) ? this.createInstallVSIXExtensionTask(manifest, extension, options) : this.createInstallFromGalleryExtensionTask(extension, options);
- }
-
- private async installExtension(manifest: IExtensionManifest, extension: string | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise {
- // only cache gallery extensions tasks
- if (!isString(extension)) {
- let installExtensionTask = this.installingExtensions.get(new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key());
- if (installExtensionTask) {
- this.logService.info('Extensions is already requested to install', extension.identifier.id);
- return installExtensionTask.waitUntilTaskIsFinished();
- }
- options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ };
- }
-
- const allInstallExtensionTasks: { task: InstallExtensionTask, manifest: IExtensionManifest }[] = [];
- const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = [];
- const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options);
- if (!isString(extension)) {
- this.installingExtensions.set(new ExtensionIdentifierWithVersion(installExtensionTask.identifier, manifest.version).key(), installExtensionTask);
- }
- this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension });
- this.logService.info('Installing extension:', installExtensionTask.identifier.id);
- allInstallExtensionTasks.push({ task: installExtensionTask, manifest });
- let installExtensionHasDependents: boolean = false;
-
- try {
- if (options.donotIncludePackAndDependencies) {
- this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id);
- } else {
- try {
- const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack);
- for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) {
- installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier));
- if (this.installingExtensions.has(new ExtensionIdentifierWithVersion(gallery.identifier, gallery.version).key())) {
- this.logService.info('Extension is already requested to install', gallery.identifier.id);
- } else {
- const task = this.createInstallExtensionTask(manifest, gallery, { ...options, donotIncludePackAndDependencies: true });
- this.installingExtensions.set(new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key(), task);
- this._onInstallExtension.fire({ identifier: task.identifier, source: gallery });
- this.logService.info('Installing extension:', task.identifier.id);
- allInstallExtensionTasks.push({ task, manifest });
- }
- }
- } catch (error) {
- this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id);
- this.logService.error(error);
- throw error;
- }
- }
-
- const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => {
- result.set(task.identifier.id.toLowerCase(), { task, manifest });
- return result;
- }, new Map());
-
- while (extensionsToInstallMap.size) {
- let extensionsToInstall;
- const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase())));
- if (extensionsWithoutDepsToInstall.length) {
- extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall
- /* If the main extension has no dependents remove it and install it at the end */
- : extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents));
- } else {
- this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id));
- extensionsToInstall = [...extensionsToInstallMap.values()];
- }
-
- // Install extensions in parallel and wait until all extensions are installed / failed
- const result = await Promise.allSettled(extensionsToInstall.map(async ({ task }) => {
- try {
- const local = await task.run();
- installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source });
- } catch (error) {
- this.logService.error('Error while installing the extension:', task.identifier.id);
- this.logService.error(error);
- throw error;
- } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); }
- }));
-
- // Collect the errors
- const errors = result.reduce((errors, r) => { if (r.status === 'rejected') { errors.push(r.reason); } return errors; }, []);
- // If there are errors, throw the error.
- if (errors.length) { throw joinErrors(errors); }
- }
-
- installResults.forEach(({ identifier }) => this.logService.info(`Extensions installed successfully:`, identifier.id));
- this._onDidInstallExtensions.fire(installResults);
- return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local;
-
- } catch (error) {
-
- // cancel all tasks
- allInstallExtensionTasks.forEach(({ task }) => task.cancel());
-
- // rollback installed extensions
- if (installResults.length) {
- try {
- await this.extensionsScanner.setUninstalled(...installResults.map(({ local }) => local));
- this.logService.info('Rollback: Uninstalled extensions', ...installResults.map(({ identifier }) => identifier.id));
- } catch (error) {
- // ignore error
- this.logService.warn('Error while rolling back extensions', getErrorMessage(error));
- }
- }
-
- this.logService.error(`Failed to install extension:`, installExtensionTask.identifier.id, getErrorMessage(error));
- this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source })));
-
- if (error instanceof Error) {
- error.name = error && (error).code ? (error).code : ERROR_UNKNOWN;
- }
- throw error;
- } finally {
- /* Remove the gallery tasks from the cache */
- for (const { task, manifest } of allInstallExtensionTasks) {
- if (!isString(task.source)) {
- const key = new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key();
- if (!this.installingExtensions.delete(key)) {
- this.logService.warn('Installation task is not found in the cache', key);
- }
- }
- }
- }
- }
-
- private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean): Promise<{ gallery: IGalleryExtension, manifest: IExtensionManifest }[]> {
- if (!this.galleryService.isEnabled()) {
- return [];
- }
-
- let installed = await this.getInstalled();
- const knownIdentifiers = [extensionIdentifier, ...(installed).map(i => i.identifier)];
-
- const allDependenciesAndPacks: { gallery: IGalleryExtension, manifest: IExtensionManifest }[] = [];
- const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise => {
- const dependenciesAndPackExtensions: string[] = manifest.extensionDependencies || [];
- if (manifest.extensionPack) {
- const existing = getOnlyNewlyAddedFromExtensionPack ? installed.find(e => areSameExtensions(e.identifier, extensionIdentifier)) : undefined;
- for (const extension of manifest.extensionPack) {
- // add only those extensions which are new in currently installed extension
- if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) {
- if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) {
- dependenciesAndPackExtensions.push(extension);
- }
- }
- }
- }
-
- if (dependenciesAndPackExtensions.length) {
- // filter out installed and known extensions
- const identifiers = [...knownIdentifiers, ...allDependenciesAndPacks.map(r => r.gallery.identifier)];
- const names = dependenciesAndPackExtensions.filter(id => identifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id })));
- if (names.length) {
- const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None);
- for (const galleryExtension of galleryResult.firstPage) {
- if (identifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {
- continue;
- }
- const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension);
- if (!await this.canInstall(compatibleExtension)) {
- this.logService.info('Skipping the extension as it cannot be installed', compatibleExtension.identifier.id);
- continue;
- }
- const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None);
- if (manifest === null) {
- throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, INSTALL_ERROR_VALIDATING);
- }
- allDependenciesAndPacks.push({ gallery: compatibleExtension, manifest });
- await collectDependenciesAndPackExtensionsToInstall(compatibleExtension.identifier, manifest);
- }
- }
- }
- };
-
- await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest);
- installed = await this.getInstalled();
- return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier)));
- }
-
- private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise {
- if (await this.isMalicious(extension)) {
- throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), INSTALL_ERROR_MALICIOUS);
- }
-
- const compatibleExtension = await this.galleryService.getCompatibleExtension(extension);
- if (!compatibleExtension) {
- throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE);
- }
-
- return compatibleExtension;
- }
-
- async reinstallFromGallery(extension: ILocalExtension): Promise {
- this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id);
- if (!this.galleryService.isEnabled()) {
- throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
- }
-
- const galleryExtension = await this.findGalleryExtension(extension);
- if (!galleryExtension) {
- throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"));
- }
-
- await this.extensionsScanner.setUninstalled(extension);
- try {
- await this.extensionsScanner.removeUninstalledExtension(extension);
- } catch (e) {
- throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e)));
- }
-
- await this.installFromGallery(galleryExtension);
- }
-
- private async isMalicious(extension: IGalleryExtension): Promise {
- const report = await this.getExtensionsReport();
- return getMaliciousExtensionsSet(report).has(extension.identifier.id);
- }
-
- async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise {
- this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
- const installed = await this.getInstalled(ExtensionType.User);
- const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier));
- if (!extensionToUninstall) {
- throw new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name));
- }
-
- try {
- await this.checkForDependenciesAndUninstall(extensionToUninstall, installed, options);
- } catch (error) {
- throw joinErrors(error);
- }
- }
-
- async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise {
- this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id);
- local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), ...metadata });
- this.manifestCache.invalidate();
- return local;
- }
-
- async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise {
- this.logService.trace('ExtensionManagementService#updateExtensionScope', local.identifier.id);
- local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), isMachineScoped });
- this.manifestCache.invalidate();
- return local;
- }
-
- private async findGalleryExtension(local: ILocalExtension): Promise {
- if (local.identifier.uuid) {
- const galleryExtension = await this.findGalleryExtensionById(local.identifier.uuid);
- return galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id);
- }
- return this.findGalleryExtensionByName(local.identifier.id);
- }
-
- private async findGalleryExtensionById(uuid: string): Promise {
- const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None);
- return galleryResult.firstPage[0];
- }
-
- private async findGalleryExtensionByName(name: string): Promise {
- const galleryResult = await this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None);
- return galleryResult.firstPage[0];
- }
-
- private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], options: UninstallOptions): Promise {
- try {
- await this.preUninstallExtension(extension);
- const packedExtensions = options.donotIncludePack ? [] : this.getAllPackExtensionsToUninstall(extension, installed);
- await this.uninstallExtensions(extension, packedExtensions, installed, options);
- } catch (error) {
- await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
- throw error;
- }
- await this.postUninstallExtension(extension);
- }
-
- private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], options: UninstallOptions): Promise {
- const extensionsToUninstall = [extension, ...otherExtensionsToUninstall];
- if (!options.donotCheckDependents) {
- for (const e of extensionsToUninstall) {
- this.checkForDependents(e, extensionsToUninstall, installed, extension);
- }
- }
- await Promises.settled([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]);
- }
-
- private checkForDependents(extension: ILocalExtension, extensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], extensionToUninstall: ILocalExtension): void {
- const dependents = this.getDependents(extension, installed);
- if (dependents.length) {
- const remainingDependents = dependents.filter(dependent => extensionsToUninstall.indexOf(dependent) === -1);
- if (remainingDependents.length) {
- throw new Error(this.getDependentsErrorMessage(extension, remainingDependents, extensionToUninstall));
- }
- }
- }
-
- private getDependentsErrorMessage(dependingExtension: ILocalExtension, dependents: ILocalExtension[], extensionToUninstall: ILocalExtension): string {
- if (extensionToUninstall === dependingExtension) {
- if (dependents.length === 1) {
- return nls.localize('singleDependentError', "Cannot uninstall '{0}' extension. '{1}' extension depends on this.",
- extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
- }
- if (dependents.length === 2) {
- return nls.localize('twoDependentsError', "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.",
- extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
- }
- return nls.localize('multipleDependentsError', "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.",
- extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
- }
- if (dependents.length === 1) {
- return nls.localize('singleIndirectDependentError', "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.",
- extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
- || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
- }
- if (dependents.length === 2) {
- return nls.localize('twoIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.",
- extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
- || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
- }
- return nls.localize('multipleIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.",
- extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
- || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
-
- }
-
- private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
- if (checked.indexOf(extension) !== -1) {
- return [];
- }
- checked.push(extension);
- const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : [];
- if (extensionsPack.length) {
- const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier)));
- const packOfPackedExtensions: ILocalExtension[] = [];
- for (const packedExtension of packedExtensions) {
- packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
- }
- return [...packedExtensions, ...packOfPackedExtensions];
- }
- return [];
- }
-
- private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] {
- return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
- }
-
- private async doUninstall(extension: ILocalExtension): Promise {
- try {
- await this.preUninstallExtension(extension);
- await this.uninstallExtension(extension);
- } catch (error) {
- await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
- throw error;
- }
- await this.postUninstallExtension(extension);
- }
-
- private async preUninstallExtension(extension: ILocalExtension): Promise {
- const exists = await pfs.Promises.exists(extension.location.fsPath);
- if (!exists) {
- throw new Error(nls.localize('notExists', "Could not find extension"));
- }
- this.logService.info('Uninstalling extension:', extension.identifier.id);
- this._onUninstallExtension.fire(extension.identifier);
- }
-
- private async uninstallExtension(local: ILocalExtension): Promise {
- let promise = this.uninstallingExtensions.get(local.identifier.id);
- if (!promise) {
- // Set all versions of the extension as uninstalled
- promise = createCancelablePromise(async () => {
- const userExtensions = await this.extensionsScanner.scanUserExtensions(false);
- await this.extensionsScanner.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier)));
- });
- this.uninstallingExtensions.set(local.identifier.id, promise);
- promise.finally(() => this.uninstallingExtensions.delete(local.identifier.id));
- }
- return promise;
- }
-
- private async postUninstallExtension(extension: ILocalExtension, error?: Error): Promise {
- if (error) {
- this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
- } else {
- this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
- // only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
- if (extension.identifier.uuid) {
- try {
- await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
- } catch (error) { /* ignore */ }
- }
- }
- reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error);
- const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined;
- this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: errorcode });
- }
-
- getInstalled(type: ExtensionType | null = null): Promise {
- return this.extensionsScanner.scanExtensions(type);
- }
-
- removeDeprecatedExtensions(): Promise {
- return this.extensionsScanner.cleanUp();
- }
-
- getExtensionsReport(): Promise {
- const now = new Date().getTime();
-
- if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
- this.reportedExtensions = this.updateReportCache();
- this.lastReportTimestamp = now;
- }
-
- return this.reportedExtensions;
- }
-
- private async updateReportCache(): Promise {
- try {
- this.logService.trace('ExtensionManagementService.refreshReportedCache');
- const result = await this.galleryService.getExtensionsReport();
- this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
- return result;
- } catch (err) {
- this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
- return [];
- }
- }
-
}
-function joinErrors(errorOrErrors: (Error | string) | (Array)): Error {
- const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
- if (errors.length === 1) {
- return errors[0] instanceof Error ? errors[0] : new Error(errors[0]);
- }
- return errors.reduce((previousValue: Error, currentValue: Error | string) => {
- return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
- }, new Error(''));
-}
-
-function reportTelemetry(telemetryService: ITelemetryService, eventName: string, extensionData: any, duration?: number, error?: Error): void {
- const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined;
- /* __GDPR__
- "extensionGallery:install" : {
- "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
- "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
- "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
- "recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
- "${include}": [
- "${GalleryExtensionTelemetryData}"
- ]
- }
- */
- /* __GDPR__
- "extensionGallery:uninstall" : {
- "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
- "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
- "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
- "${include}": [
- "${GalleryExtensionTelemetryData}"
- ]
- }
- */
- /* __GDPR__
- "extensionGallery:update" : {
- "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
- "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
- "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
- "${include}": [
- "${GalleryExtensionTelemetryData}"
- ]
- }
- */
- telemetryService.publicLogError(eventName, { ...extensionData, success: !error, duration, errorcode });
-}
-
-abstract class AbstractInstallExtensionTask implements InstallExtensionTask {
-
- private readonly barrier = new Barrier();
- private cancellablePromise: CancelablePromise | undefined;
+abstract class AbstractInstallExtensionTask extends AbstractExtensionTask implements IInstallExtensionTask {
protected _operation = InstallOperation.Install;
get operation() { return this._operation; }
constructor(
readonly identifier: IExtensionIdentifier,
- readonly source: string | IGalleryExtension,
+ readonly source: URI | IGalleryExtension,
protected readonly extensionsScanner: ExtensionsScanner,
protected readonly logService: ILogService,
) {
- }
-
- async waitUntilTaskIsFinished(): Promise {
- await this.barrier.wait();
- return this.cancellablePromise!;
- }
-
- async run(): Promise {
- if (!this.cancellablePromise) {
- this.cancellablePromise = createCancelablePromise(token => this.install(token));
- }
- this.barrier.open();
- return this.cancellablePromise;
- }
-
- cancel(): void {
- if (!this.cancellablePromise) {
- this.cancellablePromise = createCancelablePromise(token => {
- return new Promise((c, e) => {
- const disposable = token.onCancellationRequested(() => {
- disposable.dispose();
- e(canceled());
- });
- });
- });
- this.barrier.open();
- }
- this.cancellablePromise.cancel();
+ super();
}
protected async installExtension(installableExtension: InstallableExtension, token: CancellationToken): Promise {
@@ -801,7 +244,6 @@ abstract class AbstractInstallExtensionTask implements InstallExtensionTask {
return local;
}
- abstract install(token: CancellationToken): Promise;
}
class InstallGalleryExtensionTask extends AbstractInstallExtensionTask {
@@ -810,45 +252,29 @@ class InstallGalleryExtensionTask extends AbstractInstallExtensionTask {
private readonly gallery: IGalleryExtension,
private readonly options: InstallOptions,
private readonly extensionsDownloader: ExtensionsDownloader,
- private readonly telemetryService: ITelemetryService,
extensionsScanner: ExtensionsScanner,
logService: ILogService,
) {
super(gallery.identifier, gallery, extensionsScanner, logService);
}
- install(token: CancellationToken): Promise {
- return this.installGalleryExtension(this.gallery, this.options, token);
- }
-
- private async installGalleryExtension(gallery: IGalleryExtension, options: InstallOptions, token: CancellationToken): Promise {
- const startTime = new Date().getTime();
- try {
- const installed = await this.extensionsScanner.scanExtensions(null);
- const existingExtension = installed.find(i => areSameExtensions(i.identifier, gallery.identifier));
- if (existingExtension) {
- this._operation = InstallOperation.Update;
- }
-
- const installableExtension = await this.downloadInstallableExtension(gallery, this._operation);
- installableExtension.metadata.isMachineScoped = options.isMachineScoped || existingExtension?.isMachineScoped;
- installableExtension.metadata.isBuiltin = options.isBuiltin || existingExtension?.isBuiltin;
-
- const local = await this.installExtension(installableExtension, token);
- if (existingExtension && semver.neq(existingExtension.manifest.version, gallery.version)) {
- await this.extensionsScanner.setUninstalled(existingExtension);
- }
- try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ }
- reportTelemetry(this.telemetryService, this.getTelemetryEvent(this._operation), getGalleryExtensionTelemetryData(gallery), new Date().getTime() - startTime, undefined);
- return local;
- } catch (error) {
- reportTelemetry(this.telemetryService, this.getTelemetryEvent(this._operation), getGalleryExtensionTelemetryData(gallery), new Date().getTime() - startTime, error);
- throw error;
+ protected async doRun(token: CancellationToken): Promise {
+ const installed = await this.extensionsScanner.scanExtensions(null);
+ const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.gallery.identifier));
+ if (existingExtension) {
+ this._operation = InstallOperation.Update;
}
- }
- private getTelemetryEvent(operation: InstallOperation): string {
- return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install';
+ const installableExtension = await this.downloadInstallableExtension(this.gallery, this._operation);
+ installableExtension.metadata.isMachineScoped = this.options.isMachineScoped || existingExtension?.isMachineScoped;
+ installableExtension.metadata.isBuiltin = this.options.isBuiltin || existingExtension?.isBuiltin;
+
+ const local = await this.installExtension(installableExtension, token);
+ if (existingExtension && semver.neq(existingExtension.manifest.version, this.gallery.version)) {
+ await this.extensionsScanner.setUninstalled(existingExtension);
+ }
+ try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ }
+ return local;
}
private async downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): Promise> {
@@ -880,16 +306,16 @@ class InstallVSIXTask extends AbstractInstallExtensionTask {
constructor(
private readonly manifest: IExtensionManifest,
- private readonly zipPath: string,
+ private readonly location: URI,
private readonly options: InstallOptions,
private readonly galleryService: IExtensionGalleryService,
extensionsScanner: ExtensionsScanner,
logService: ILogService
) {
- super({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, zipPath, extensionsScanner, logService);
+ super({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, location, extensionsScanner, logService);
}
- async install(token: CancellationToken): Promise {
+ protected async doRun(token: CancellationToken): Promise {
const identifierWithVersion = new ExtensionIdentifierWithVersion(this.identifier, this.manifest.version);
const installedExtensions = await this.extensionsScanner.scanExtensions(ExtensionType.User);
const existing = installedExtensions.find(i => areSameExtensions(this.identifier, i.identifier));
@@ -921,7 +347,7 @@ class InstallVSIXTask extends AbstractInstallExtensionTask {
}
}
- return this.installExtension({ zipPath: this.zipPath, identifierWithVersion, metadata }, token);
+ return this.installExtension({ zipPath: path.resolve(this.location.fsPath), identifierWithVersion, metadata }, token);
}
private async getMetadata(name: string, token: CancellationToken): Promise {
@@ -936,3 +362,41 @@ class InstallVSIXTask extends AbstractInstallExtensionTask {
return {};
}
}
+
+class UninstallExtensionTask extends AbstractExtensionTask implements IUninstallExtensionTask {
+
+ constructor(
+ readonly extension: ILocalExtension,
+ private readonly options: UninstallExtensionTaskOptions,
+ private readonly extensionsScanner: ExtensionsScanner
+ ) { super(); }
+
+ protected async doRun(token: CancellationToken): Promise {
+ const toUninstall: ILocalExtension[] = [];
+ const userExtensions = await this.extensionsScanner.scanUserExtensions(false);
+ if (this.options.versionOnly) {
+ const extensionIdentifierWithVersion = new ExtensionIdentifierWithVersion(this.extension.identifier, this.extension.manifest.version);
+ toUninstall.push(...userExtensions.filter(u => extensionIdentifierWithVersion.equals(new ExtensionIdentifierWithVersion(u.identifier, u.manifest.version))));
+ } else {
+ toUninstall.push(...userExtensions.filter(u => areSameExtensions(u.identifier, this.extension.identifier)));
+ }
+
+ if (!toUninstall.length) {
+ throw new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", this.extension.manifest.displayName || this.extension.manifest.name));
+ }
+ await this.extensionsScanner.setUninstalled(...toUninstall);
+
+ if (this.options.remove) {
+ for (const extension of toUninstall) {
+ try {
+ if (!token.isCancellationRequested) {
+ await this.extensionsScanner.removeUninstalledExtension(extension);
+ }
+ } catch (e) {
+ throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e)));
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts
index ec3b0dc72e1..10d47b82f00 100644
--- a/src/vs/platform/list/browser/listService.ts
+++ b/src/vs/platform/list/browser/listService.ts
@@ -110,10 +110,7 @@ export const WorkbenchListHasSelectionOrFocus = new RawContextKey('list
export const WorkbenchListDoubleSelection = new RawContextKey('listDoubleSelection', false);
export const WorkbenchListMultiSelection = new RawContextKey('listMultiSelection', false);
export const WorkbenchListSelectionNavigation = new RawContextKey('listSelectionNavigation', false);
-export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey('listSupportsKeyboardNavigation', true);
export const WorkbenchListAutomaticKeyboardNavigationKey = 'listAutomaticKeyboardNavigation';
-export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey(WorkbenchListAutomaticKeyboardNavigationKey, true);
-export let didBindWorkbenchListAutomaticKeyboardNavigation = false;
function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: ListWidget): IContextKeyService {
const result = contextKeyService.createScoped(widget.getHTMLElement());
@@ -240,7 +237,7 @@ export class WorkbenchList extends List {
this.themeService = themeService;
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
- this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
+ this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
listSelectionNavigation.set(Boolean(options.selectionNavigation));
@@ -374,7 +371,7 @@ export class WorkbenchPagedList extends PagedList {
this.horizontalScrolling = options.horizontalScrolling;
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
- this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
+ this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
listSelectionNavigation.set(Boolean(options.selectionNavigation));
@@ -499,7 +496,7 @@ export class WorkbenchTable extends Table {
this.themeService = themeService;
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
- this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
+ this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
listSelectionNavigation.set(Boolean(options.selectionNavigation));
@@ -1021,13 +1018,6 @@ function workbenchTreeDataPreamble boolean | undefined, disposable: IDisposable } {
- WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService);
-
- if (!didBindWorkbenchListAutomaticKeyboardNavigation) {
- WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService);
- didBindWorkbenchListAutomaticKeyboardNavigation = true;
- }
-
const getAutomaticKeyboardNavigation = () => {
// give priority to the context key value to disable this completely
let automaticKeyboardNavigation = Boolean(contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey));
@@ -1099,7 +1089,7 @@ class WorkbenchTreeInternals {
this.contextKeyService = createScopedContextKeyService(contextKeyService, tree);
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
- this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
+ this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
listSelectionNavigation.set(Boolean(options.selectionNavigation));
@@ -1156,7 +1146,7 @@ class WorkbenchTreeInternals {
newOptions = { ...newOptions, renderIndentGuides };
}
if (e.affectsConfiguration(listSmoothScrolling)) {
- const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));
+ const smoothScrolling = Boolean(!!configurationService.getValue(listSmoothScrolling));
newOptions = { ...newOptions, smoothScrolling };
}
if (e.affectsConfiguration(keyboardNavigationSettingKey)) {
@@ -1166,7 +1156,7 @@ class WorkbenchTreeInternals {
newOptions = { ...newOptions, automaticKeyboardNavigation: getAutomaticKeyboardNavigation() };
}
if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) {
- const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));
+ const horizontalScrolling = Boolean(!!configurationService.getValue(horizontalScrollingKey));
newOptions = { ...newOptions, horizontalScrolling };
}
if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {
diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts
index d2170c42c66..a1e68d17aad 100644
--- a/src/vs/platform/telemetry/common/telemetryService.ts
+++ b/src/vs/platform/telemetry/common/telemetryService.ts
@@ -225,7 +225,7 @@ Registry.as(Extensions.Configuration).registerConfigurat
'default': true,
'restricted': true,
'scope': ConfigurationScope.APPLICATION,
- 'tags': ['usesOnlineServices']
+ 'tags': ['usesOnlineServices', 'telemetry']
}
}
});
diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts
index ebb842c80fb..66e9e200f87 100644
--- a/src/vs/platform/terminal/node/terminalProfiles.ts
+++ b/src/vs/platform/terminal/node/terminalProfiles.ts
@@ -37,7 +37,7 @@ export function detectAvailableProfiles(
includeDetectedProfiles,
fsProvider,
logService,
- configurationService.getValue(TerminalSettingId.UseWslProfiles) !== false,
+ configurationService.getValue(TerminalSettingId.UseWslProfiles) !== false,
profiles && typeof profiles === 'object' ? { ...profiles } : configurationService.getValue<{ [key: string]: ITerminalProfileObject }>(TerminalSettingId.ProfilesWindows),
typeof defaultProfile === 'string' ? defaultProfile : configurationService.getValue(TerminalSettingId.DefaultProfileWindows),
testPwshSourcePaths,
diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts
index 81d832df692..0a901c1f049 100644
--- a/src/vs/platform/update/electron-main/updateService.win32.ts
+++ b/src/vs/platform/update/electron-main/updateService.win32.ts
@@ -149,7 +149,7 @@ export class Win32UpdateService extends AbstractUpdateService {
.then(() => updatePackagePath);
});
}).then(packagePath => {
- const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates');
+ const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates');
this.availableUpdate = { packagePath };
diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts
index c0610c0db04..e69eca91b94 100644
--- a/src/vs/platform/userDataSync/common/keybindingsSync.ts
+++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts
@@ -331,15 +331,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
private syncKeybindingsPerPlatform(): boolean {
- let userValue = this.configurationService.inspect('settingsSync.keybindingsPerPlatform').userValue;
+ let userValue = !!this.configurationService.inspect('settingsSync.keybindingsPerPlatform').userValue;
if (userValue !== undefined) {
return userValue;
}
- userValue = this.configurationService.inspect('sync.keybindingsPerPlatform').userValue;
+ userValue = !!this.configurationService.inspect('sync.keybindingsPerPlatform').userValue;
if (userValue !== undefined) {
return userValue;
}
- return this.configurationService.getValue('settingsSync.keybindingsPerPlatform');
+ return !!this.configurationService.getValue('settingsSync.keybindingsPerPlatform');
}
}
diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts
index 0d258c8e48c..a7494770538 100644
--- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts
+++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts
@@ -61,14 +61,14 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
}
- isEnabled(defaultEnablement?: boolean): boolean {
+ isEnabled(): boolean {
switch (this.environmentService.sync) {
case 'on':
return true;
case 'off':
return false;
}
- return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, !!defaultEnablement);
+ return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, false);
}
canToggleEnablement(): boolean {
diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts
index b440572b157..960f38b305e 100644
--- a/src/vs/workbench/api/browser/mainThreadTesting.ts
+++ b/src/vs/workbench/api/browser/mainThreadTesting.ts
@@ -37,7 +37,6 @@ const reviveDiff = (diff: TestsDiff) => {
export class MainThreadTesting extends Disposable implements MainThreadTestingShape, ITestRootProvider {
private readonly proxy: ExtHostTestingShape;
private readonly diffListener = this._register(new MutableDisposable());
- private readonly testSubscriptions = new Map();
private readonly testProviderRegistrations = new Map();
constructor(
@@ -205,10 +204,10 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
public override dispose() {
super.dispose();
- for (const subscription of this.testSubscriptions.values()) {
+ for (const subscription of this.testProviderRegistrations.values()) {
subscription.dispose();
}
- this.testSubscriptions.clear();
+ this.testProviderRegistrations.clear();
}
private withLiveRun(runId: string, fn: (run: LiveTestResult) => T): T | undefined {
diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts
index 22b005c0989..c41f16f2d00 100644
--- a/src/vs/workbench/api/common/extHostComments.ts
+++ b/src/vs/workbench/api/common/extHostComments.ts
@@ -383,7 +383,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
});
const that = this;
- this.value = Object.freeze({
+ this.value = {
get uri() { return that.uri; },
get range() { return that.range; },
set range(value: vscode.Range) { that.range = value; },
@@ -400,7 +400,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
dispose: () => {
that.dispose();
}
- });
+ };
}
diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts
index 21d9cbdedbe..e36e5403ee1 100644
--- a/src/vs/workbench/api/common/extHostTerminalService.ts
+++ b/src/vs/workbench/api/common/extHostTerminalService.ts
@@ -845,9 +845,11 @@ function asTerminalIcon(iconPath?: vscode.Uri | { light: vscode.Uri; dark: vscod
if (!iconPath) {
return undefined;
}
- if (!('id' in iconPath)) {
+
+ if (typeof iconPath === 'string' || !('id' in iconPath)) {
return iconPath;
}
+
return {
id: iconPath.id,
color: iconPath.color as ThemeColor
diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts
index 9748e2fd654..d7618a4c8e8 100644
--- a/src/vs/workbench/api/node/extHostDebugService.ts
+++ b/src/vs/workbench/api/node/extHostDebugService.ts
@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import type * as vscode from 'vscode';
import * as platform from 'vs/base/common/platform';
-import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes';
+import { DebugAdapterExecutable, ThemeIcon } from 'vs/workbench/api/common/extHostTypes';
import { ExecutableDebugAdapter, SocketDebugAdapter, NamedPipeDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
@@ -98,6 +98,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
shellArgs: shellArgs,
cwd: args.cwd,
name: terminalName,
+ iconPath: new ThemeIcon('debug'),
};
giveShellTimeToInitialize = true;
terminal = this._terminalService.createTerminalFromOptions(options, {
diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts
index b12c147f01f..0d032154fe0 100644
--- a/src/vs/workbench/browser/actions/developerActions.ts
+++ b/src/vs/workbench/browser/actions/developerActions.ts
@@ -207,7 +207,7 @@ class ToggleScreencastModeAction extends Action2 {
const event = new StandardKeyboardEvent(e);
const shortcut = keybindingService.softDispatch(event, event.target);
- if (shortcut || !configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) {
+ if (shortcut || !configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) {
if (
event.ctrlKey || event.altKey || event.metaKey || event.shiftKey
|| length > 20
diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts
index 93162fac56b..3f91a43463c 100644
--- a/src/vs/workbench/browser/layout.ts
+++ b/src/vs/workbench/browser/layout.ts
@@ -394,13 +394,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (!this.state.zenMode.active) {
// Statusbar visibility
- const newStatusbarHiddenValue = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE);
+ const newStatusbarHiddenValue = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE);
if (newStatusbarHiddenValue !== this.state.statusBar.hidden) {
this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout);
}
// Activitybar visibility
- const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE);
+ const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE);
if (newActivityBarHiddenValue !== this.state.activityBar.hidden) {
this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout);
}
diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css
index cf008e43f65..1b9c8bfda9e 100644
--- a/src/vs/workbench/browser/media/style.css
+++ b/src/vs/workbench/browser/media/style.css
@@ -192,10 +192,10 @@ body.web {
.monaco-workbench .select-container:after {
content: "\eab4";
font-family: codicon;
- font-size: 14px;
- width: 14px;
- height: 14px;
- line-height: 14px;
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+ line-height: 16px;
position: absolute;
top: 0;
bottom: 0;
diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts
index 8c9a23b2132..d331360b573 100644
--- a/src/vs/workbench/browser/parts/editor/editorActions.ts
+++ b/src/vs/workbench/browser/parts/editor/editorActions.ts
@@ -43,7 +43,7 @@ export class ExecuteCommandAction extends Action {
}
}
-export class BaseSplitEditorAction extends Action {
+abstract class AbstractSplitEditorAction extends Action {
private readonly toDispose = this._register(new DisposableStore());
private direction: GroupDirection;
@@ -77,7 +77,7 @@ export class BaseSplitEditorAction extends Action {
}
}
-export class SplitEditorAction extends BaseSplitEditorAction {
+export class SplitEditorAction extends AbstractSplitEditorAction {
static readonly ID = 'workbench.action.splitEditor';
static readonly LABEL = localize('splitEditor', "Split Editor");
@@ -92,7 +92,7 @@ export class SplitEditorAction extends BaseSplitEditorAction {
}
}
-export class SplitEditorOrthogonalAction extends BaseSplitEditorAction {
+export class SplitEditorOrthogonalAction extends AbstractSplitEditorAction {
static readonly ID = 'workbench.action.splitEditorOrthogonal';
static readonly LABEL = localize('splitEditorOrthogonal', "Split Editor Orthogonal");
@@ -259,7 +259,7 @@ export class FocusActiveGroupAction extends Action {
}
}
-export abstract class BaseFocusGroupAction extends Action {
+abstract class AbstractFocusGroupAction extends Action {
constructor(
id: string,
@@ -278,7 +278,7 @@ export abstract class BaseFocusGroupAction extends Action {
}
}
-export class FocusFirstGroupAction extends BaseFocusGroupAction {
+export class FocusFirstGroupAction extends AbstractFocusGroupAction {
static readonly ID = 'workbench.action.focusFirstEditorGroup';
static readonly LABEL = localize('focusFirstEditorGroup', "Focus First Editor Group");
@@ -292,7 +292,7 @@ export class FocusFirstGroupAction extends BaseFocusGroupAction {
}
}
-export class FocusLastGroupAction extends BaseFocusGroupAction {
+export class FocusLastGroupAction extends AbstractFocusGroupAction {
static readonly ID = 'workbench.action.focusLastEditorGroup';
static readonly LABEL = localize('focusLastEditorGroup', "Focus Last Editor Group");
@@ -306,7 +306,7 @@ export class FocusLastGroupAction extends BaseFocusGroupAction {
}
}
-export class FocusNextGroup extends BaseFocusGroupAction {
+export class FocusNextGroup extends AbstractFocusGroupAction {
static readonly ID = 'workbench.action.focusNextGroup';
static readonly LABEL = localize('focusNextGroup', "Focus Next Editor Group");
@@ -320,7 +320,7 @@ export class FocusNextGroup extends BaseFocusGroupAction {
}
}
-export class FocusPreviousGroup extends BaseFocusGroupAction {
+export class FocusPreviousGroup extends AbstractFocusGroupAction {
static readonly ID = 'workbench.action.focusPreviousGroup';
static readonly LABEL = localize('focusPreviousGroup', "Focus Previous Editor Group");
@@ -334,7 +334,7 @@ export class FocusPreviousGroup extends BaseFocusGroupAction {
}
}
-export class FocusLeftGroup extends BaseFocusGroupAction {
+export class FocusLeftGroup extends AbstractFocusGroupAction {
static readonly ID = 'workbench.action.focusLeftGroup';
static readonly LABEL = localize('focusLeftGroup', "Focus Left Editor Group");
@@ -348,7 +348,7 @@ export class FocusLeftGroup extends BaseFocusGroupAction {
}
}
-export class FocusRightGroup extends BaseFocusGroupAction {
+export class FocusRightGroup extends AbstractFocusGroupAction {
static readonly ID = 'workbench.action.focusRightGroup';
static readonly LABEL = localize('focusRightGroup', "Focus Right Editor Group");
@@ -362,7 +362,7 @@ export class FocusRightGroup extends BaseFocusGroupAction {
}
}
-export class FocusAboveGroup extends BaseFocusGroupAction {
+export class FocusAboveGroup extends AbstractFocusGroupAction {
static readonly ID = 'workbench.action.focusAboveGroup';
static readonly LABEL = localize('focusAboveGroup', "Focus Above Editor Group");
@@ -376,7 +376,7 @@ export class FocusAboveGroup extends BaseFocusGroupAction {
}
}
-export class FocusBelowGroup extends BaseFocusGroupAction {
+export class FocusBelowGroup extends AbstractFocusGroupAction {
static readonly ID = 'workbench.action.focusBelowGroup';
static readonly LABEL = localize('focusBelowGroup', "Focus Below Editor Group");
@@ -534,7 +534,7 @@ export class CloseLeftEditorsInGroupAction extends Action {
}
}
-abstract class BaseCloseAllAction extends Action {
+abstract class AbstractCloseAllAction extends Action {
constructor(
id: string,
@@ -647,7 +647,7 @@ abstract class BaseCloseAllAction extends Action {
protected abstract doCloseAll(): Promise;
}
-export class CloseAllEditorsAction extends BaseCloseAllAction {
+export class CloseAllEditorsAction extends AbstractCloseAllAction {
static readonly ID = 'workbench.action.closeAllEditors';
static readonly LABEL = localize('closeAllEditors', "Close All Editors");
@@ -673,7 +673,7 @@ export class CloseAllEditorsAction extends BaseCloseAllAction {
}
}
-export class CloseAllEditorGroupsAction extends BaseCloseAllAction {
+export class CloseAllEditorGroupsAction extends AbstractCloseAllAction {
static readonly ID = 'workbench.action.closeAllGroups';
static readonly LABEL = localize('closeAllGroups', "Close All Editor Groups");
@@ -750,7 +750,7 @@ export class CloseEditorInAllGroupsAction extends Action {
}
}
-class BaseMoveCopyGroupAction extends Action {
+abstract class AbstractMoveCopyGroupAction extends Action {
constructor(
id: string,
@@ -815,7 +815,7 @@ class BaseMoveCopyGroupAction extends Action {
}
}
-class BaseMoveGroupAction extends BaseMoveCopyGroupAction {
+abstract class AbstractMoveGroupAction extends AbstractMoveCopyGroupAction {
constructor(
id: string,
@@ -827,7 +827,7 @@ class BaseMoveGroupAction extends BaseMoveCopyGroupAction {
}
}
-export class MoveGroupLeftAction extends BaseMoveGroupAction {
+export class MoveGroupLeftAction extends AbstractMoveGroupAction {
static readonly ID = 'workbench.action.moveActiveEditorGroupLeft';
static readonly LABEL = localize('moveActiveGroupLeft', "Move Editor Group Left");
@@ -841,7 +841,7 @@ export class MoveGroupLeftAction extends BaseMoveGroupAction {
}
}
-export class MoveGroupRightAction extends BaseMoveGroupAction {
+export class MoveGroupRightAction extends AbstractMoveGroupAction {
static readonly ID = 'workbench.action.moveActiveEditorGroupRight';
static readonly LABEL = localize('moveActiveGroupRight', "Move Editor Group Right");
@@ -855,7 +855,7 @@ export class MoveGroupRightAction extends BaseMoveGroupAction {
}
}
-export class MoveGroupUpAction extends BaseMoveGroupAction {
+export class MoveGroupUpAction extends AbstractMoveGroupAction {
static readonly ID = 'workbench.action.moveActiveEditorGroupUp';
static readonly LABEL = localize('moveActiveGroupUp', "Move Editor Group Up");
@@ -869,7 +869,7 @@ export class MoveGroupUpAction extends BaseMoveGroupAction {
}
}
-export class MoveGroupDownAction extends BaseMoveGroupAction {
+export class MoveGroupDownAction extends AbstractMoveGroupAction {
static readonly ID = 'workbench.action.moveActiveEditorGroupDown';
static readonly LABEL = localize('moveActiveGroupDown', "Move Editor Group Down");
@@ -883,7 +883,7 @@ export class MoveGroupDownAction extends BaseMoveGroupAction {
}
}
-class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction {
+abstract class AbstractDuplicateGroupAction extends AbstractMoveCopyGroupAction {
constructor(
id: string,
@@ -895,7 +895,7 @@ class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction {
}
}
-export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction {
+export class DuplicateGroupLeftAction extends AbstractDuplicateGroupAction {
static readonly ID = 'workbench.action.duplicateActiveEditorGroupLeft';
static readonly LABEL = localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left");
@@ -909,7 +909,7 @@ export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction {
}
}
-export class DuplicateGroupRightAction extends BaseDuplicateGroupAction {
+export class DuplicateGroupRightAction extends AbstractDuplicateGroupAction {
static readonly ID = 'workbench.action.duplicateActiveEditorGroupRight';
static readonly LABEL = localize('duplicateActiveGroupRight', "Duplicate Editor Group Right");
@@ -923,7 +923,7 @@ export class DuplicateGroupRightAction extends BaseDuplicateGroupAction {
}
}
-export class DuplicateGroupUpAction extends BaseDuplicateGroupAction {
+export class DuplicateGroupUpAction extends AbstractDuplicateGroupAction {
static readonly ID = 'workbench.action.duplicateActiveEditorGroupUp';
static readonly LABEL = localize('duplicateActiveGroupUp', "Duplicate Editor Group Up");
@@ -937,7 +937,7 @@ export class DuplicateGroupUpAction extends BaseDuplicateGroupAction {
}
}
-export class DuplicateGroupDownAction extends BaseDuplicateGroupAction {
+export class DuplicateGroupDownAction extends AbstractDuplicateGroupAction {
static readonly ID = 'workbench.action.duplicateActiveEditorGroupDown';
static readonly LABEL = localize('duplicateActiveGroupDown', "Duplicate Editor Group Down");
@@ -1016,7 +1016,7 @@ export class MaximizeGroupAction extends Action {
}
}
-export abstract class BaseNavigateEditorAction extends Action {
+abstract class AbstractNavigateEditorAction extends Action {
constructor(
id: string,
@@ -1047,7 +1047,7 @@ export abstract class BaseNavigateEditorAction extends Action {
protected abstract navigate(): IEditorIdentifier | undefined;
}
-export class OpenNextEditor extends BaseNavigateEditorAction {
+export class OpenNextEditor extends AbstractNavigateEditorAction {
static readonly ID = 'workbench.action.nextEditor';
static readonly LABEL = localize('openNextEditor', "Open Next Editor");
@@ -1082,7 +1082,7 @@ export class OpenNextEditor extends BaseNavigateEditorAction {
}
}
-export class OpenPreviousEditor extends BaseNavigateEditorAction {
+export class OpenPreviousEditor extends AbstractNavigateEditorAction {
static readonly ID = 'workbench.action.previousEditor';
static readonly LABEL = localize('openPreviousEditor', "Open Previous Editor");
@@ -1117,7 +1117,7 @@ export class OpenPreviousEditor extends BaseNavigateEditorAction {
}
}
-export class OpenNextEditorInGroup extends BaseNavigateEditorAction {
+export class OpenNextEditorInGroup extends AbstractNavigateEditorAction {
static readonly ID = 'workbench.action.nextEditorInGroup';
static readonly LABEL = localize('nextEditorInGroup', "Open Next Editor in Group");
@@ -1140,7 +1140,7 @@ export class OpenNextEditorInGroup extends BaseNavigateEditorAction {
}
}
-export class OpenPreviousEditorInGroup extends BaseNavigateEditorAction {
+export class OpenPreviousEditorInGroup extends AbstractNavigateEditorAction {
static readonly ID = 'workbench.action.previousEditorInGroup';
static readonly LABEL = localize('openPreviousEditorInGroup', "Open Previous Editor in Group");
@@ -1163,7 +1163,7 @@ export class OpenPreviousEditorInGroup extends BaseNavigateEditorAction {
}
}
-export class OpenFirstEditorInGroup extends BaseNavigateEditorAction {
+export class OpenFirstEditorInGroup extends AbstractNavigateEditorAction {
static readonly ID = 'workbench.action.firstEditorInGroup';
static readonly LABEL = localize('firstEditorInGroup', "Open First Editor in Group");
@@ -1185,7 +1185,7 @@ export class OpenFirstEditorInGroup extends BaseNavigateEditorAction {
}
}
-export class OpenLastEditorInGroup extends BaseNavigateEditorAction {
+export class OpenLastEditorInGroup extends AbstractNavigateEditorAction {
static readonly ID = 'workbench.action.lastEditorInGroup';
static readonly LABEL = localize('lastEditorInGroup', "Open Last Editor in Group");
@@ -1359,7 +1359,7 @@ export class ShowAllEditorsByMostRecentlyUsedAction extends Action {
}
}
-export class BaseQuickAccessEditorAction extends Action {
+abstract class AbstractQuickAccessEditorAction extends Action {
constructor(
id: string,
@@ -1382,7 +1382,7 @@ export class BaseQuickAccessEditorAction extends Action {
}
}
-export class QuickAccessPreviousRecentlyUsedEditorAction extends BaseQuickAccessEditorAction {
+export class QuickAccessPreviousRecentlyUsedEditorAction extends AbstractQuickAccessEditorAction {
static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditor';
static readonly LABEL = localize('quickOpenPreviousRecentlyUsedEditor', "Quick Open Previous Recently Used Editor");
@@ -1397,7 +1397,7 @@ export class QuickAccessPreviousRecentlyUsedEditorAction extends BaseQuickAccess
}
}
-export class QuickAccessLeastRecentlyUsedEditorAction extends BaseQuickAccessEditorAction {
+export class QuickAccessLeastRecentlyUsedEditorAction extends AbstractQuickAccessEditorAction {
static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditor';
static readonly LABEL = localize('quickOpenLeastRecentlyUsedEditor', "Quick Open Least Recently Used Editor");
@@ -1412,7 +1412,7 @@ export class QuickAccessLeastRecentlyUsedEditorAction extends BaseQuickAccessEdi
}
}
-export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction {
+export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends AbstractQuickAccessEditorAction {
static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup';
static readonly LABEL = localize('quickOpenPreviousRecentlyUsedEditorInGroup', "Quick Open Previous Recently Used Editor in Group");
@@ -1427,7 +1427,7 @@ export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends BaseQuic
}
}
-export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction {
+export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends AbstractQuickAccessEditorAction {
static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditorInGroup';
static readonly LABEL = localize('quickOpenLeastRecentlyUsedEditorInGroup', "Quick Open Least Recently Used Editor in Group");
@@ -1817,7 +1817,7 @@ export class EditorLayoutTwoRowsRightAction extends ExecuteCommandAction {
}
}
-export class BaseCreateEditorGroupAction extends Action {
+abstract class AbstractCreateEditorGroupAction extends Action {
constructor(
id: string,
@@ -1833,7 +1833,7 @@ export class BaseCreateEditorGroupAction extends Action {
}
}
-export class NewEditorGroupLeftAction extends BaseCreateEditorGroupAction {
+export class NewEditorGroupLeftAction extends AbstractCreateEditorGroupAction {
static readonly ID = 'workbench.action.newGroupLeft';
static readonly LABEL = localize('newEditorLeft', "New Editor Group to the Left");
@@ -1847,7 +1847,7 @@ export class NewEditorGroupLeftAction extends BaseCreateEditorGroupAction {
}
}
-export class NewEditorGroupRightAction extends BaseCreateEditorGroupAction {
+export class NewEditorGroupRightAction extends AbstractCreateEditorGroupAction {
static readonly ID = 'workbench.action.newGroupRight';
static readonly LABEL = localize('newEditorRight', "New Editor Group to the Right");
@@ -1861,7 +1861,7 @@ export class NewEditorGroupRightAction extends BaseCreateEditorGroupAction {
}
}
-export class NewEditorGroupAboveAction extends BaseCreateEditorGroupAction {
+export class NewEditorGroupAboveAction extends AbstractCreateEditorGroupAction {
static readonly ID = 'workbench.action.newGroupAbove';
static readonly LABEL = localize('newEditorAbove', "New Editor Group Above");
@@ -1875,7 +1875,7 @@ export class NewEditorGroupAboveAction extends BaseCreateEditorGroupAction {
}
}
-export class NewEditorGroupBelowAction extends BaseCreateEditorGroupAction {
+export class NewEditorGroupBelowAction extends AbstractCreateEditorGroupAction {
static readonly ID = 'workbench.action.newGroupBelow';
static readonly LABEL = localize('newEditorBelow', "New Editor Group Below");
diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts
index e6f7d0ed4ae..bd7d98cd903 100644
--- a/src/vs/workbench/browser/parts/editor/editorCommands.ts
+++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts
@@ -352,14 +352,14 @@ function registerDiffEditorCommands(): void {
function toggleDiffSideBySide(accessor: ServicesAccessor): void {
const configurationService = accessor.get(IConfigurationService);
- const newValue = !configurationService.getValue('diffEditor.renderSideBySide');
+ const newValue = !configurationService.getValue('diffEditor.renderSideBySide');
configurationService.updateValue('diffEditor.renderSideBySide', newValue);
}
function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void {
const configurationService = accessor.get(IConfigurationService);
- const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace');
+ const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace');
configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue);
}
@@ -948,7 +948,7 @@ function registerOtherEditorCommands(): void {
handler: accessor => {
const configurationService = accessor.get(IConfigurationService);
- const currentSetting = configurationService.getValue('workbench.editor.enablePreview');
+ const currentSetting = configurationService.getValue('workbench.editor.enablePreview');
const newSetting = currentSetting === true ? false : true;
configurationService.updateValue('workbench.editor.enablePreview', newSetting);
}
diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts
index b07b6a1c1f7..55d2421bd09 100644
--- a/src/vs/workbench/browser/parts/editor/editorPane.ts
+++ b/src/vs/workbench/browser/parts/editor/editorPane.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Composite } from 'vs/workbench/browser/composite';
-import { IEditorPane, GroupIdentifier, IEditorMemento, IEditorOpenContext } from 'vs/workbench/common/editor';
+import { IEditorPane, GroupIdentifier, IEditorMemento, IEditorOpenContext, isEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -19,9 +19,10 @@ import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs
import { MementoObject } from 'vs/workbench/common/memento';
import { joinPath, IExtUri, isEqual } from 'vs/base/common/resources';
import { indexOfPath } from 'vs/base/common/extpath';
-import { IDisposable } from 'vs/base/common/lifecycle';
+import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
+import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
/**
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
@@ -153,12 +154,12 @@ export abstract class EditorPane extends Composite implements IEditorPane {
this._group = group;
}
- protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento {
+ protected getEditorMemento(editorGroupService: IEditorGroupsService, configurationService: ITextResourceConfigurationService, key: string, limit: number = 10): IEditorMemento {
const mementoKey = `${this.getId()}${key}`;
let editorMemento = EditorPane.EDITOR_MEMENTOS.get(mementoKey);
if (!editorMemento) {
- editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE), limit, editorGroupService);
+ editorMemento = this._register(new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE), limit, editorGroupService, configurationService));
EditorPane.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
}
@@ -186,21 +187,39 @@ export abstract class EditorPane extends Composite implements IEditorPane {
}
interface MapGroupToMemento {
- [group: number]: T;
+ [group: GroupIdentifier]: T;
}
-export class EditorMemento implements IEditorMemento {
+export class EditorMemento extends Disposable implements IEditorMemento {
+
+ private static readonly SHARED_EDITOR_STATE = -1; // pick a number < 0 to be outside group id range
+
private cache: LRUCache> | undefined;
private cleanedUp = false;
private editorDisposables: Map | undefined;
+ private shareEditorState = false;
constructor(
readonly id: string,
private key: string,
private memento: MementoObject,
private limit: number,
- private editorGroupService: IEditorGroupsService
- ) { }
+ private editorGroupService: IEditorGroupsService,
+ private configurationService: ITextResourceConfigurationService
+ ) {
+ super();
+
+ this.updateConfiguration();
+ this.registerListeners();
+ }
+
+ private registerListeners(): void {
+ this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration()));
+ }
+
+ private updateConfiguration(): void {
+ this.shareEditorState = this.configurationService.getValue(undefined, 'workbench.editor.sharedViewState') === true;
+ }
saveEditorState(group: IEditorGroup, resource: URI, state: T): void;
saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void;
@@ -212,16 +231,23 @@ export class EditorMemento implements IEditorMemento {
const cache = this.doLoad();
- let mementoForResource = cache.get(resource.toString());
- if (!mementoForResource) {
- mementoForResource = Object.create(null) as MapGroupToMemento;
- cache.set(resource.toString(), mementoForResource);
+ // Ensure mementos for resource map
+ let mementosForResource = cache.get(resource.toString());
+ if (!mementosForResource) {
+ mementosForResource = Object.create(null) as MapGroupToMemento;
+ cache.set(resource.toString(), mementosForResource);
}
- mementoForResource[group.id] = state;
+ // Store state for group
+ mementosForResource[group.id] = state;
+
+ // Store state as most recent one based on settings
+ if (this.shareEditorState) {
+ mementosForResource[EditorMemento.SHARED_EDITOR_STATE] = state;
+ }
// Automatically clear when editor input gets disposed if any
- if (resourceOrEditor instanceof EditorInput) {
+ if (isEditorInput(resourceOrEditor)) {
this.clearEditorStateOnDispose(resource, resourceOrEditor);
}
}
@@ -236,18 +262,28 @@ export class EditorMemento implements IEditorMemento {
const cache = this.doLoad();
- const mementoForResource = cache.get(resource.toString());
- if (mementoForResource) {
- return mementoForResource[group.id];
+ const mementosForResource = cache.get(resource.toString());
+ if (mementosForResource) {
+ let mementoForResourceAndGroup = mementosForResource[group.id];
+
+ // Return state for group if present
+ if (mementoForResourceAndGroup) {
+ return mementoForResourceAndGroup;
+ }
+
+ // Return most recent state based on settings otherwise
+ if (this.shareEditorState) {
+ return mementosForResource[EditorMemento.SHARED_EDITOR_STATE];
+ }
}
- return;
+ return undefined;
}
clearEditorState(resource: URI, group?: IEditorGroup): void;
clearEditorState(editor: EditorInput, group?: IEditorGroup): void;
clearEditorState(resourceOrEditor: URI | EditorInput, group?: IEditorGroup): void {
- if (resourceOrEditor instanceof EditorInput) {
+ if (isEditorInput(resourceOrEditor)) {
this.editorDisposables?.delete(resourceOrEditor);
}
@@ -255,16 +291,20 @@ export class EditorMemento implements IEditorMemento {
if (resource) {
const cache = this.doLoad();
+ // Clear state for group
if (group) {
- const resourceViewState = cache.get(resource.toString());
- if (resourceViewState) {
- delete resourceViewState[group.id];
+ const mementosForResource = cache.get(resource.toString());
+ if (mementosForResource) {
+ delete mementosForResource[group.id];
- if (isEmptyObject(resourceViewState)) {
+ if (isEmptyObject(mementosForResource)) {
cache.delete(resource.toString());
}
}
- } else {
+ }
+
+ // Clear state across all groups for resource
+ else {
cache.delete(resource.toString());
}
}
@@ -305,7 +345,7 @@ export class EditorMemento implements IEditorMemento {
targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved
}
- // Don't modify LRU state.
+ // Don't modify LRU state
const value = cache.get(cacheKey, Touch.None);
if (value) {
cache.delete(cacheKey);
@@ -315,7 +355,7 @@ export class EditorMemento implements IEditorMemento {
}
private doGetResource(resourceOrEditor: URI | EditorInput): URI | undefined {
- if (resourceOrEditor instanceof EditorInput) {
+ if (isEditorInput(resourceOrEditor)) {
return resourceOrEditor.resource;
}
@@ -354,12 +394,16 @@ export class EditorMemento implements IEditorMemento {
// Remove groups from states that no longer exist. Since we modify the
// cache and its is a LRU cache make a copy to ensure iteration succeeds
const entries = [...cache.entries()];
- for (const [resource, mapGroupToMemento] of entries) {
- for (const group of Object.keys(mapGroupToMemento)) {
+ for (const [resource, mapGroupToMementos] of entries) {
+ for (const group of Object.keys(mapGroupToMementos)) {
const groupId: GroupIdentifier = Number(group);
+ if (groupId === EditorMemento.SHARED_EDITOR_STATE && this.shareEditorState) {
+ continue; // skip over shared entries if sharing is enabled
+ }
+
if (!this.editorGroupService.getGroup(groupId)) {
- delete mapGroupToMemento[groupId];
- if (isEmptyObject(mapGroupToMemento)) {
+ delete mapGroupToMementos[groupId];
+ if (isEmptyObject(mapGroupToMementos)) {
cache.delete(resource);
}
}
diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts
index 76c80ca50b1..502b64ca5b7 100644
--- a/src/vs/workbench/browser/parts/editor/textEditor.ts
+++ b/src/vs/workbench/browser/parts/editor/textEditor.ts
@@ -68,7 +68,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
) {
super(id, telemetryService, themeService, storageService);
- this.editorMemento = this.getEditorMemento(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
+ this.editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this._register(this.textResourceConfigurationService.onDidChangeConfiguration(() => {
const resource = this.getActiveResource();
diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts
index 3a017b9f9b6..32af414acd3 100644
--- a/src/vs/workbench/browser/workbench.contribution.ts
+++ b/src/vs/workbench/browser/workbench.contribution.ts
@@ -194,10 +194,15 @@ const registry = Registry.as(ConfigurationExtensions.Con
},
'workbench.editor.restoreViewState': {
'type': 'boolean',
- 'description': localize('restoreViewState', "Restores the last view state (e.g. scroll position) when re-opening textual editors after they have been closed."),
+ 'markdownDescription': localize('restoreViewState', "Restores the last editor view state (e.g. scroll position) when re-opening editors after they have been closed. Editor view state is stored per editor group and discarded when a group closes. Use the `#workbench.editor.sharedViewState#` setting to use the last known view state across all editor groups in case no previous view state was found for a editor group."),
'default': true,
'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE
},
+ 'workbench.editor.sharedViewState': {
+ 'type': 'boolean',
+ 'description': localize('sharedViewState', "Preserves the most recent editor view state (e.g. scroll position) across all editor groups and restores that if no specific editor view state is found for the editor group."),
+ 'default': false
+ },
'workbench.editor.centeredLayoutAutoResize': {
'type': 'boolean',
'default': true,
diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts
index ed122cf379a..15f5e47dca8 100644
--- a/src/vs/workbench/common/editor.ts
+++ b/src/vs/workbench/common/editor.ts
@@ -618,12 +618,12 @@ export interface IEditorInput extends IDisposable {
isDisposed(): boolean;
}
-export abstract class BaseEditorInput extends Disposable {
+export abstract class AbstractEditorInput extends Disposable {
// Marker class for implementing `isEditorInput`
}
export function isEditorInput(editor: unknown): editor is IEditorInput {
- return editor instanceof BaseEditorInput;
+ return editor instanceof AbstractEditorInput;
}
export interface IEditorInputWithPreferredResource {
diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts
index 544bbdd7f43..709fcba253d 100644
--- a/src/vs/workbench/common/editor/editorInput.ts
+++ b/src/vs/workbench/common/editor/editorInput.ts
@@ -7,14 +7,14 @@ import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { firstOrDefault } from 'vs/base/common/arrays';
-import { IEditorInput, EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRevertOptions, IMoveResult, IEditorDescriptor, IEditorPane, IUntypedEditorInput, EditorResourceAccessor, BaseEditorInput, isEditorInput } from 'vs/workbench/common/editor';
+import { IEditorInput, EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRevertOptions, IMoveResult, IEditorDescriptor, IEditorPane, IUntypedEditorInput, EditorResourceAccessor, AbstractEditorInput, isEditorInput } from 'vs/workbench/common/editor';
import { isEqual } from 'vs/base/common/resources';
/**
* Editor inputs are lightweight objects that can be passed to the workbench API to open inside the editor part.
* Each editor input is mapped to an editor that is capable of opening it through the Platform facade.
*/
-export abstract class EditorInput extends BaseEditorInput implements IEditorInput {
+export abstract class EditorInput extends AbstractEditorInput implements IEditorInput {
protected readonly _onDidChangeDirty = this._register(new Emitter());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
diff --git a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
index dc6d1ecb17c..57b644f5be7 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
@@ -47,7 +47,7 @@ export class UntitledTextEditorHintContribution implements IEditorContribution {
private update(): void {
this.untitledTextHintContentWidget?.dispose();
- const configValue = this.configurationService.getValue<'text' | 'hidden'>(untitledTextEditorHintSetting);
+ const configValue = this.configurationService.getValue(untitledTextEditorHintSetting);
const model = this.editor.getModel();
if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID && configValue === 'text') {
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts
index 5632f259d02..0641561695a 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts
@@ -23,7 +23,7 @@ import { ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser
import { ConfigBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/configBasedRecommendations';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { timeout } from 'vs/base/common/async';
-import { isString } from 'vs/base/common/types';
+import { URI } from 'vs/base/common/uri';
type IgnoreRecommendationClassification = {
recommendationReason: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -216,7 +216,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void {
for (const e of results) {
- if (e.source && !isString(e.source) && e.operation === InstallOperation.Install) {
+ if (e.source && !URI.isUri(e.source) && e.operation === InstallOperation.Install) {
const extRecommendations = this.getAllRecommendationsWithReason() || {};
const recommendationReason = extRecommendations[e.source.identifier.id.toLowerCase()];
if (recommendationReason) {
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
index d90029f4857..7396079d934 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -40,7 +40,7 @@ import { FileAccess } from 'vs/base/common/network';
import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { isBoolean, isString } from 'vs/base/common/types';
+import { isBoolean } from 'vs/base/common/types';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
interface IExtensionStateProvider {
@@ -418,7 +418,7 @@ class Extensions extends Disposable {
private onInstallExtension(event: InstallExtensionEvent): void {
const { source } = event;
- if (source && !isString(source)) {
+ if (source && !URI.isUri(source)) {
const extension = this.installed.filter(e => areSameExtensions(e.identifier, source.identifier))[0]
|| this.instantiationService.createInstance(Extension, this.stateProvider, this.server, undefined, source);
this.installing.push(extension);
@@ -429,13 +429,13 @@ class Extensions extends Disposable {
private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void {
for (const event of results) {
const { local, source } = event;
- const gallery = source && !isString(source) ? source : undefined;
- const zipPath = source && isString(source) ? source : undefined;
+ const gallery = source && !URI.isUri(source) ? source : undefined;
+ const location = source && URI.isUri(source) ? source : undefined;
const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null;
this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;
let extension: Extension | undefined = installingExtension ? installingExtension
- : (zipPath || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.server, local, undefined)
+ : (location || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.server, local, undefined)
: undefined;
if (extension) {
if (local) {
diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts
index d834b54f85a..7a57e7ccb92 100644
--- a/src/vs/workbench/contrib/files/browser/fileCommands.ts
+++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts
@@ -275,7 +275,14 @@ CommandsRegistry.registerCommand({
async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, labelService: ILabelService, configurationService: IConfigurationService): Promise {
if (resources.length) {
const lineDelimiter = isWindows ? '\r\n' : '\n';
- const separator = relative ? configurationService.getValue<'/' | '\\' | undefined>('explorer.copyRelativePathSeparator') : undefined;
+
+ let separator: '/' | '\\' | undefined = undefined;
+ if (relative) {
+ const relativeSeparator = configurationService.getValue('explorer.copyRelativePathSeparator');
+ if (relativeSeparator === '/' || relativeSeparator === '\\') {
+ separator = relativeSeparator;
+ }
+ }
const text = resources.map(resource => labelService.getUriLabel(resource, { relative, noPrefix: true, separator })).join(lineDelimiter);
await clipboardService.writeText(text);
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
index 6b5c6550531..6e87acfd99b 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts
@@ -407,13 +407,16 @@ configurationRegistry.registerConfiguration({
'type': 'string',
'enum': [
'/',
- '\\'
+ '\\',
+ 'auto'
],
'enumDescriptions': [
nls.localize('copyRelativePathSeparator.slash', "Use slash as path separation character."),
nls.localize('copyRelativePathSeparator.backslash', "Use backslash as path separation character."),
+ nls.localize('copyRelativePathSeparator.auto', "Uses operating system specific path separation character."),
],
- 'description': nls.localize('copyRelativePathSeparator', "The path separation character used when copying relative file paths. Will use the operating system default unless specified."),
+ 'description': nls.localize('copyRelativePathSeparator', "The path separation character used when copying relative file paths."),
+ 'default': 'auto'
}
}
});
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
index f1792729f3a..ad2e11fc0ad 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { Event } from 'vs/base/common/event';
import * as paths from 'vs/base/common/path';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
@@ -10,17 +11,17 @@ import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor';
-import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
+import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService';
import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
-export class InteractiveEditorInput extends SideBySideEditorInput implements ICompositeNotebookEditorInput {
+export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput {
static create(instantiationService: IInstantiationService, resource: URI, inputResource: URI) {
return instantiationService.createInstance(InteractiveEditorInput, resource, inputResource);
}
- static override readonly ID: string = 'workbench.input.interactive';
+ static readonly ID: string = 'workbench.input.interactive';
override get typeId(): string {
return InteractiveEditorInput.ID;
@@ -56,6 +57,10 @@ export class InteractiveEditorInput extends SideBySideEditorInput implements ICo
private _modelService: IModelService;
private _interactiveDocumentService: IInteractiveDocumentService;
+ get primary(): EditorInput {
+ return this._notebookEditorInput;
+ }
+
constructor(
resource: URI,
@@ -65,7 +70,7 @@ export class InteractiveEditorInput extends SideBySideEditorInput implements ICo
@IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService
) {
const input = NotebookEditorInput.create(instantiationService, resource, 'interactive', {});
- super(undefined, undefined, input, input);
+ super();
this._notebookEditorInput = input;
this._register(this._notebookEditorInput);
this._inputResource = inputResource;
@@ -74,6 +79,25 @@ export class InteractiveEditorInput extends SideBySideEditorInput implements ICo
this._inputModel = null;
this._modelService = modelService;
this._interactiveDocumentService = interactiveDocumentService;
+
+ this._registerListeners();
+ }
+
+ private _registerListeners(): void {
+ const oncePrimaryDisposed = Event.once(this.primary.onWillDispose);
+ this._register(oncePrimaryDisposed(() => {
+ if (!this.isDisposed()) {
+ this.dispose();
+ }
+ }));
+
+ // Re-emit some events from the primary side to the outside
+ this._register(this.primary.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
+ this._register(this.primary.onDidChangeLabel(() => this._onDidChangeLabel.fire()));
+
+ // Re-emit some events from both sides to the outside
+ this._register(this.primary.onDidChangeCapabilities(() => this._onDidChangeCapabilities.fire()));
+
}
override isDirty() {
diff --git a/src/vs/workbench/contrib/list/browser/list.contribution.ts b/src/vs/workbench/contrib/list/browser/list.contribution.ts
new file mode 100644
index 00000000000..f396bf16f84
--- /dev/null
+++ b/src/vs/workbench/contrib/list/browser/list.contribution.ts
@@ -0,0 +1,25 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
+import { WorkbenchListAutomaticKeyboardNavigationKey } from 'vs/platform/list/browser/listService';
+import { Registry } from 'vs/platform/registry/common/platform';
+import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
+
+export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey('listSupportsKeyboardNavigation', true);
+export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey(WorkbenchListAutomaticKeyboardNavigationKey, true);
+
+export class ListContext implements IWorkbenchContribution {
+
+ constructor(
+ @IContextKeyService contextKeyService: IContextKeyService
+ ) {
+ WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService);
+ WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService);
+ }
+}
+
+Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ListContext, LifecyclePhase.Starting);
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts
index 0dc0380c832..fb3438d7024 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts
@@ -177,7 +177,7 @@ class NotebookOutlineRenderer implements ITreeRenderer(OutlineConfigKeys.problemsBadges);
+ const useBadges = this._configurationService.getValue(OutlineConfigKeys.problemsBadges);
if (!useBadges) {
template.decoration.classList.remove('bubble');
template.decoration.innerText = '';
@@ -189,7 +189,7 @@ class NotebookOutlineRenderer implements ITreeRenderer 9 ? '9+' : String(markerInfo.count);
}
const color = this._themeService.getColorTheme().getColor(markerInfo.topSev === MarkerSeverity.Error ? listErrorForeground : listWarningForeground);
- const useColors = this._configurationService.getValue(OutlineConfigKeys.problemsColors);
+ const useColors = this._configurationService.getValue(OutlineConfigKeys.problemsColors);
if (!useColors) {
template.container.style.removeProperty('--outline-element-color');
template.decoration.style.setProperty('--outline-element-color', color?.toString() ?? 'inherit');
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts
index 7c0055bb36f..270d13d1446 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts
@@ -230,12 +230,12 @@ class ToggleRenderAction extends Action2 {
const configurationService = accessor.get(IConfigurationService);
if (this.toggleOutputs !== undefined) {
- const oldValue = configurationService.getValue('notebook.diff.ignoreOutputs');
+ const oldValue = configurationService.getValue('notebook.diff.ignoreOutputs');
configurationService.updateValue('notebook.diff.ignoreOutputs', !oldValue);
}
if (this.toggleMetadata !== undefined) {
- const oldValue = configurationService.getValue('notebook.diff.ignoreMetadata');
+ const oldValue = configurationService.getValue('notebook.diff.ignoreMetadata');
configurationService.updateValue('notebook.diff.ignoreMetadata', !oldValue);
}
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
index 2917a78b201..ef5a4fa441c 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
@@ -33,6 +33,7 @@ import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction } from 'vs/base/common/actions';
import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/browser/notebookKernelActionViewItem';
+import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
@@ -67,9 +68,10 @@ export class NotebookEditor extends EditorPane {
@INotebookEditorService private readonly _notebookWidgetService: INotebookEditorService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IFileService private readonly fileService: IFileService,
+ @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService
) {
super(NotebookEditor.ID, telemetryService, themeService, storageService);
- this._editorMemento = this.getEditorMemento(_editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
+ this._editorMemento = this.getEditorMemento(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onDidChangeFileSystemProvider(e.scheme)));
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onDidChangeFileSystemProvider(e.scheme)));
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
index dd2275b89c1..540da8eb753 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
@@ -33,16 +33,13 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
-import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
+import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
-import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane';
-import { IEditorMemento } from 'vs/workbench/common/editor';
-import { Memento, MementoObject } from 'vs/workbench/common/memento';
import { PANEL_BORDER } from 'vs/workbench/common/theme';
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
-import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, INotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_ID, NOTEBOOK_OUTPUT_FOCUSED, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, INotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/notebookEditorDecorations';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager';
@@ -62,7 +59,6 @@ import { CellKind, ExperimentalUseMarkdownRenderer, SelectionStateType } from 'v
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
-import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { readFontInfo } from 'vs/editor/browser/config/configuration';
@@ -209,7 +205,6 @@ export function getDefaultNotebookCreationOptions() {
}
export class NotebookEditorWidget extends Disposable implements INotebookEditor {
- private static readonly EDITOR_MEMENTOS = new Map>();
private _overlayContainer!: HTMLElement;
private _notebookTopToolbarContainer!: HTMLElement;
private _notebookTopToolbar!: NotebookEditorToolbar;
@@ -240,7 +235,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
private _outputRenderer: OutputRenderer;
protected readonly _contributions = new Map();
private _scrollBeyondLastLine: boolean;
- private readonly _memento: Memento;
private readonly _onDidFocusEmitter = this._register(new Emitter());
public readonly onDidFocus = this._onDidFocusEmitter.event;
private readonly _onDidBlurEmitter = this._register(new Emitter());
@@ -364,8 +358,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
}));
- this._memento = new Memento(NOTEBOOK_EDITOR_ID, storageService);
-
this._outputRenderer = this._register(new OutputRenderer(this, this.instantiationService));
this._scrollBeyondLastLine = this.configurationService.getValue('editor.scrollBeyondLastLine');
@@ -526,23 +518,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
//#region Editor Core
-
- protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento {
- const mementoKey = `${NOTEBOOK_EDITOR_ID}${key}`;
-
- let editorMemento = NotebookEditorWidget.EDITOR_MEMENTOS.get(mementoKey);
- if (!editorMemento) {
- editorMemento = new EditorMemento(NOTEBOOK_EDITOR_ID, key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService);
- NotebookEditorWidget.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
- }
-
- return editorMemento as IEditorMemento;
- }
-
- protected getMemento(scope: StorageScope): MementoObject {
- return this._memento.getMemento(scope, StorageTarget.MACHINE);
- }
-
private _updateForNotebookConfiguration() {
if (!this._overlayContainer) {
return;
diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts
index 2faa0e20875..248c22d0acb 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts
@@ -59,6 +59,7 @@ const SETTINGS_EDITOR_COMMAND_FOCUS_UP = 'settings.action.focusLevelUp';
const SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON = 'settings.switchToJSON';
const SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED = 'settings.filterByModified';
const SETTINGS_EDITOR_COMMAND_FILTER_ONLINE = 'settings.filterByOnline';
+const SETTINGS_EDITOR_COMMAND_FILTER_TELEMETRY = 'settings.filterByTelemetry';
const SETTINGS_EDITOR_COMMAND_FILTER_UNTRUSTED = 'settings.filterUntrusted';
const SETTINGS_COMMAND_OPEN_SETTINGS = 'workbench.action.openSettings';
@@ -476,6 +477,28 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
order: 2
});
+ registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: SETTINGS_EDITOR_COMMAND_FILTER_TELEMETRY,
+ title: { value: nls.localize('showTelemtrySettings', "Telemetry Settings"), original: 'Telemetry Settings' },
+ menu: {
+ id: MenuId.MenubarPreferencesMenu,
+ group: '1_settings',
+ order: 3,
+ }
+ });
+ }
+ run(accessor: ServicesAccessor) {
+ const editorPane = accessor.get(IEditorService).activeEditorPane;
+ if (editorPane instanceof SettingsEditor2) {
+ editorPane.focusSearch('@tag:telemetry');
+ } else {
+ accessor.get(IPreferencesService).openSettings(false, '@tag:telemetry');
+ }
+ }
+ });
+
registerAction2(class extends Action2 {
constructor() {
super({
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
index 77caeea2dbb..8f0618bab1a 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
@@ -53,6 +53,7 @@ import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSyn
import { preferencesClearInputIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
+import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
export const enum SettingsFocusContext {
Search,
@@ -96,6 +97,7 @@ export class SettingsEditor2 extends EditorPane {
`@tag:${WORKSPACE_TRUST_SETTING_TAG}`,
'@tag:sync',
'@tag:usesOnlineServices',
+ '@tag:telemetry',
`@${ID_SETTING_TAG}`,
`@${EXTENSION_SETTING_TAG}`,
`@${FEATURE_SETTING_TAG}scm`,
@@ -185,6 +187,7 @@ export class SettingsEditor2 extends EditorPane {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService,
+ @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@IThemeService themeService: IThemeService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -215,7 +218,7 @@ export class SettingsEditor2 extends EditorPane {
this.scheduledRefreshes = new Map();
- this.editorMemento = this.getEditorMemento(editorGroupService, SETTINGS_EDITOR_STATE_KEY);
+ this.editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, SETTINGS_EDITOR_STATE_KEY);
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.source !== ConfigurationTarget.DEFAULT) {
diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
index b36a2512d4e..5aa40c75ea2 100644
--- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
+++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
@@ -2885,7 +2885,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return Promise.resolve(undefined);
}
content = pickTemplateResult.content;
- let editorConfig = this.configurationService.getValue();
+ let editorConfig = this.configurationService.getValue() as any;
if (editorConfig.editor.insertSpaces) {
content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + ' '.repeat(s2.length * editorConfig.editor.tabSize));
}
diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
index 36f392d987b..517811539b9 100644
--- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
+++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
@@ -55,7 +55,8 @@ export class TaskQuickPick extends Disposable {
}
private showDetail(): boolean {
- return this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG);
+ // Ensure invalid values get converted into boolean values
+ return !!this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG);
}
private guessTaskLabel(task: Task | ConfiguringTask): string {
diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
index 5c6d26d0384..f4b084de4a0 100644
--- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
+++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
@@ -62,6 +62,7 @@ interface ActiveTerminalData {
terminal: ITerminalInstance;
task: Task;
promise: Promise;
+ state?: TaskEventKind;
}
class InstanceManager {
@@ -409,6 +410,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
this.removeInstances(task);
}
+ private fireTaskEvent(event: TaskEvent) {
+ if (event.__task) {
+ const activeTask = this.activeTasks[event.__task.getMapKey()];
+ if (activeTask) {
+ activeTask.state = event.kind;
+ }
+ }
+ this._onDidStateChange.fire(event);
+ }
+
public terminate(task: Task): Promise {
let activeTerminal = this.activeTasks[task.getMapKey()];
if (!activeTerminal) {
@@ -421,7 +432,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
let task = activeTerminal.task;
try {
onExit.dispose();
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Terminated, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Terminated, task));
} catch (error) {
// Do nothing.
}
@@ -441,7 +452,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
let task = terminalData.task;
try {
onExit.dispose();
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Terminated, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Terminated, task));
} catch (error) {
// Do nothing.
}
@@ -476,9 +487,9 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
let dependencyTask = await resolver.resolve(dependency.uri, dependency.task!);
if (dependencyTask) {
let key = dependencyTask.getMapKey();
- let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined;
+ let promise = this.activeTasks[key] ? this.getDependencyPromise(this.activeTasks[key]) : undefined;
if (!promise) {
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.DependsOnStarted, task));
encounteredDependencies.add(task.getCommonTaskId());
promise = this.executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved);
}
@@ -532,6 +543,30 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}
}
+ private createInactiveDependencyPromise(task: Task): Promise {
+ return new Promise(resolve => {
+ const taskInactiveDisposable = this.onDidStateChange(taskEvent => {
+ if ((taskEvent.kind === TaskEventKind.Inactive) && (taskEvent.__task === task)) {
+ taskInactiveDisposable.dispose();
+ resolve({ exitCode: 0 });
+ }
+ });
+ });
+ }
+
+ private async getDependencyPromise(task: ActiveTerminalData): Promise {
+ if (!task.task.configurationProperties.isBackground) {
+ return task.promise;
+ }
+ if (!task.task.configurationProperties.problemMatchers || task.task.configurationProperties.problemMatchers.length === 0) {
+ return task.promise;
+ }
+ if (task.state === TaskEventKind.Inactive) {
+ return { exitCode: 0 };
+ }
+ return this.createInactiveDependencyPromise(task.task);
+ }
+
private async executeDependencyTask(task: Task, resolver: ITaskResolver, trigger: string, encounteredDependencies: Set, alreadyResolved?: Map): Promise {
// If the task is a background task with a watching problem matcher, we don't wait for the whole task to finish,
// just for the problem matcher to go inactive.
@@ -539,14 +574,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
return this.executeTask(task, resolver, trigger, encounteredDependencies, alreadyResolved);
}
- const inactivePromise = new Promise(resolve => {
- const taskInactiveDisposable = this._onDidStateChange.event(taskEvent => {
- if ((taskEvent.kind === TaskEventKind.Inactive) && (taskEvent.__task === task)) {
- taskInactiveDisposable.dispose();
- resolve({ exitCode: 0 });
- }
- });
- });
+ const inactivePromise = this.createInactiveDependencyPromise(task);
return Promise.race([inactivePromise, this.executeTask(task, resolver, trigger, encounteredDependencies, alreadyResolved)]);
}
@@ -584,7 +612,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
private async acquireInput(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, variables: Set, alreadyResolved: Map): Promise {
const resolved = await this.resolveVariablesFromSet(taskSystemInfo, workspaceFolder, task, variables, alreadyResolved);
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.AcquiredInput, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.AcquiredInput, task));
return resolved;
}
@@ -689,7 +717,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder);
} else {
// Allows the taskExecutions array to be updated in the extension host
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.End, task));
return Promise.resolve({ exitCode: 0 });
}
}, reason => {
@@ -723,7 +751,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
return this.acquireInput(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables, alreadyResolved).then((resolvedVariables) => {
if (!resolvedVariables) {
// Allows the taskExecutions array to be updated in the extension host
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.End, task));
return { exitCode: 0 };
}
this.currentTask.resolvedVariables = resolvedVariables;
@@ -756,13 +784,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
if (event.kind === ProblemCollectorEventKind.BackgroundProcessingBegins) {
eventCounter++;
this.busyTasks[mapKey] = task;
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Active, task));
} else if (event.kind === ProblemCollectorEventKind.BackgroundProcessingEnds) {
eventCounter--;
if (this.busyTasks[mapKey]) {
delete this.busyTasks[mapKey];
}
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Inactive, task));
if (eventCounter === 0) {
if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity &&
(watchingProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error)) {
@@ -793,13 +821,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
let processStartedSignaled = false;
terminal.processReady.then(() => {
if (!processStartedSignaled) {
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
processStartedSignaled = true;
}
}, (_error) => {
this.logService.error('Task terminal process never got ready');
});
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId));
let skipLine: boolean = (!!task.command.presentation && task.command.presentation.echo);
const onData = terminal.onLineData((line) => {
if (skipLine) {
@@ -824,7 +852,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
delete this.busyTasks[mapKey];
}
this.removeFromActiveTasks(task);
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Changed));
if (exitCode !== undefined) {
// Only keep a reference to the terminal if it is not being disposed.
switch (task.command.presentation!.panel) {
@@ -850,18 +878,17 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
watchingProblemMatcher.done();
watchingProblemMatcher.dispose();
if (!processStartedSignaled) {
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
processStartedSignaled = true;
}
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode));
for (let i = 0; i < eventCounter; i++) {
- let event = TaskEvent.create(TaskEventKind.Inactive, task);
- this._onDidStateChange.fire(event);
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Inactive, task));
}
eventCounter = 0;
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.End, task));
toDispose.dispose();
resolve({ exitCode });
});
@@ -879,16 +906,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
let processStartedSignaled = false;
terminal.processReady.then(() => {
if (!processStartedSignaled) {
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
processStartedSignaled = true;
}
}, (_error) => {
// The process never got ready. Need to think how to handle this.
});
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId, resolver.values));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Start, task, terminal.instanceId, resolver.values));
const mapKey = task.getMapKey();
this.busyTasks[mapKey] = task;
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Active, task));
let problemMatchers = await this.resolveMatchers(resolver, task.configurationProperties.problemMatchers);
let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService, ProblemHandlingStrategy.Clean, this.fileService);
this.terminalStatusManager.addTerminal(task, terminal, startStopProblemMatcher);
@@ -905,7 +932,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
onExit.dispose();
let key = task.getMapKey();
this.removeFromActiveTasks(task);
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Changed));
if (exitCode !== undefined) {
// Only keep a reference to the terminal if it is not being disposed.
switch (task.command.presentation!.panel) {
@@ -939,16 +966,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
startStopProblemMatcher.dispose();
}, 100);
if (!processStartedSignaled && terminal) {
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!));
processStartedSignaled = true;
}
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode));
if (this.busyTasks[mapKey]) {
delete this.busyTasks[mapKey];
}
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task));
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Inactive, task));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.End, task));
resolve({ exitCode });
});
});
@@ -962,7 +989,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
this.terminalGroupService.showPanel(task.command.presentation.focus);
}
this.activeTasks[task.getMapKey()] = { terminal, task, promise };
- this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed));
+ this.fireTaskEvent(TaskEvent.create(TaskEventKind.Changed));
return promise.then((summary) => {
try {
let telemetryEvent: TelemetryEvent = {
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts
index 9b3530e26fb..e1421365a86 100644
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts
@@ -91,7 +91,7 @@ export class TerminalLink extends DisposableStore implements ILink {
// Clear out scheduler until next hover event
this._tooltipScheduler?.dispose();
this._tooltipScheduler = undefined;
- }, this._configurationService.getValue('workbench.hover.delay'));
+ }, this._configurationService.getValue('workbench.hover.delay'));
this.add(this._tooltipScheduler);
this._tooltipScheduler.schedule();
}
diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css
index 1ac20b3dfdf..018a797e85b 100644
--- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css
+++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css
@@ -172,3 +172,7 @@
.xterm-underline {
text-decoration: underline;
}
+
+.xterm-strikethrough {
+ text-decoration: line-through;
+}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
index 46fd746bd59..e03b17d1586 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
@@ -29,9 +29,10 @@ import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/qu
import { ICreateTerminalOptions, ITerminalProfile, TerminalLocation, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
+import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions';
-import { Direction, IRemoteTerminalService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { Direction, IRemoteTerminalService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
import { ILocalTerminalService, IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TerminalCommandId, TERMINAL_ACTION_CATEGORY } from 'vs/workbench/contrib/terminal/common/terminal';
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
@@ -1728,7 +1729,7 @@ export function registerTerminalActions() {
});
}
async run(accessor: ServicesAccessor) {
- accessor.get(ITerminalEditorService).activeInstance?.dispose();
+ accessor.get(ICommandService).executeCommand(CLOSE_EDITOR_COMMAND_ID);
}
});
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts
index 0a7c693f782..9c046081f4e 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts
@@ -295,8 +295,8 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe
if (this.activeGroupIndex !== instanceLocation.groupIndex) {
this.activeGroupIndex = instanceLocation.groupIndex;
this._onDidChangeActiveGroup.fire(this.activeGroup);
- instanceLocation.group.setActiveInstanceByIndex(activeInstanceIndex, true);
}
+ instanceLocation.group.setActiveInstanceByIndex(activeInstanceIndex, true);
this.groups.forEach((g, i) => g.setVisible(i === instanceLocation.groupIndex));
}
@@ -326,9 +326,22 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe
moveGroup(source: ITerminalInstance, target: ITerminalInstance) {
const sourceGroup = this.getGroupForInstance(source);
const targetGroup = this.getGroupForInstance(target);
+
+ // Something went wrong
if (!sourceGroup || !targetGroup) {
return;
}
+
+ // The groups are the same, rearrange within the group
+ if (sourceGroup === targetGroup) {
+ const index = sourceGroup.terminalInstances.indexOf(target);
+ if (index !== -1) {
+ sourceGroup.moveInstance(source, index);
+ }
+ return;
+ }
+
+ // The groups differ, rearrange groups
const sourceGroupIndex = this.groups.indexOf(sourceGroup);
const targetGroupIndex = this.groups.indexOf(targetGroup);
this.groups.splice(sourceGroupIndex, 1);
diff --git a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts
index a9bf1a2ea8a..30f84c9db12 100644
--- a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts
+++ b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts
@@ -16,7 +16,7 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work
import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection';
import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
-import { RunTestsRequest, ITestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
+import { RunTestsRequest, ITestIdWithSrc, TestsDiff, TestDiffOpType } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
@@ -195,6 +195,15 @@ export class TestService extends Disposable implements ITestService {
this.providerCount.set(this.testControllers.size);
return toDisposable(() => {
+ const diff: TestsDiff = [];
+ for (const root of this.collection.rootItems) {
+ if (root.controllerId === id) {
+ diff.push([TestDiffOpType.Remove, root.item.extId]);
+ }
+ }
+
+ this.publishDiff(id, diff);
+
if (this.testControllers.delete(id)) {
this.providerCount.set(this.testControllers.size);
}
diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
index ca730c23966..c6750120870 100644
--- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
+++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
@@ -1274,7 +1274,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
}
if (syncResourceConflicts[1].some(({ remoteResource }) => isEqual(remoteResource, model.uri))) {
- return this.configurationService.getValue('diffEditor.renderSideBySide');
+ return this.configurationService.getValue('diffEditor.renderSideBySide');
}
return false;
diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css
index 3b215f316a3..d50655a885e 100644
--- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css
+++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css
@@ -232,9 +232,9 @@
font-size: 13px;
box-sizing: border-box;
line-height: normal;
- margin: 8px 8px 12px;
+ margin: 8px 8px 12px 0;
padding: 3px 6px 6px;
- left: 1px;
+ left: -3px;
text-align: left;
}
diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts
index 4e14ed6342c..c8334328239 100644
--- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts
+++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts
@@ -152,12 +152,14 @@ function isWelcomePageEnabled(configurationService: IConfigurationService, conte
return welcomeEnabled.value;
}
}
- if (startupEditor.value === 'readme' && startupEditor.userValue !== 'readme') {
- console.error('Warning: `workbench.startupEditor: readme` setting ignored due to being set somewhere other than user settings');
+
+ if (startupEditor.value === 'readme' && startupEditor.userValue !== 'readme' && startupEditor.defaultValue !== 'readme') {
+ console.error(`Warning: 'workbench.startupEditor: readme' setting ignored due to being set somewhere other than user or default settings (user=${startupEditor.userValue}, default=${startupEditor.defaultValue})`);
}
return startupEditor.value === 'welcomePage'
|| startupEditor.value === 'legacy_welcomePage'
|| startupEditor.userValue === 'readme'
+ || startupEditor.defaultValue === 'readme'
|| (contextService.getWorkbenchState() === WorkbenchState.EMPTY && (startupEditor.value === 'legacy_welcomePageInEmptyWorkbench' || startupEditor.value === 'welcomePageInEmptyWorkbench'));
}
diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts
index 58982ff59cb..04124d77076 100644
--- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts
+++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts
@@ -15,7 +15,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
import { IOpenerService } from 'vs/platform/opener/common/opener';
-import { IModelService } from 'vs/editor/common/services/modelService';
+import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@@ -70,7 +70,7 @@ export class WalkThroughPart extends EditorPane {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
- @IModelService modelService: IModelService,
+ @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IOpenerService private readonly openerService: IOpenerService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@@ -83,7 +83,7 @@ export class WalkThroughPart extends EditorPane {
) {
super(WalkThroughPart.ID, telemetryService, themeService, storageService);
this.editorFocus = WALK_THROUGH_FOCUS.bindTo(this.contextKeyService);
- this.editorMemento = this.getEditorMemento(editorGroupService, WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY);
+ this.editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY);
}
createEditor(container: HTMLElement): void {
@@ -248,11 +248,11 @@ export class WalkThroughPart extends EditorPane {
}
private getArrowScrollHeight() {
- let fontSize = this.configurationService.getValue('editor.fontSize');
+ let fontSize = this.configurationService.getValue('editor.fontSize');
if (typeof fontSize !== 'number' || fontSize < 1) {
fontSize = 12;
}
- return 3 * fontSize;
+ return 3 * (fontSize as number);
}
pageUp() {
@@ -469,7 +469,7 @@ export class WalkThroughPart extends EditorPane {
private multiCursorModifier() {
const labels = UILabelProvider.modifierLabels[OS];
- const value = this.configurationService.getValue('editor.multiCursorModifier');
+ const value = this.configurationService.getValue('editor.multiCursorModifier');
const modifier = labels[value === 'ctrlCmd' ? (OS === OperatingSystem.Macintosh ? 'metaKey' : 'ctrlKey') : 'altKey'];
const keys = this.content.querySelectorAll('.multi-cursor-modifier');
Array.prototype.forEach.call(keys, (key: Element) => {
diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts
index ec9f288f24a..ac0a88a6580 100644
--- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts
+++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts
@@ -19,13 +19,13 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
import { Codicon } from 'vs/base/common/codicons';
import { ThemeColor } from 'vs/workbench/api/common/extHostTypes';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
+import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor';
import { shieldIcon, WorkspaceTrustEditor } from 'vs/workbench/contrib/workspace/browser/workspaceTrustEditor';
import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput';
-import { WorkspaceTrustContext, WORKSPACE_TRUST_BANNER, WORKSPACE_TRUST_EMPTY_WINDOW, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_STARTUP_PROMPT, WORKSPACE_TRUST_UNTRUSTED_FILES } from 'vs/workbench/services/workspaces/common/workspaceTrust';
+import { WORKSPACE_TRUST_BANNER, WORKSPACE_TRUST_EMPTY_WINDOW, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_STARTUP_PROMPT, WORKSPACE_TRUST_UNTRUSTED_FILES } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
@@ -55,6 +55,38 @@ const BANNER_RESTRICTED_MODE = 'workbench.banner.restrictedMode';
const STARTUP_PROMPT_SHOWN_KEY = 'workspace.trust.startupPrompt.shown';
const BANNER_RESTRICTED_MODE_DISMISSED_KEY = 'workbench.banner.restrictedMode.dismissed';
+/**
+ * Trust Context Keys
+ */
+
+export const WorkspaceTrustContext = {
+ IsEnabled: new RawContextKey('isWorkspaceTrustEnabled', false, localize('workspaceTrustEnabledCtx', "Whether the workspace trust feature is enabled.")),
+ IsTrusted: new RawContextKey('isWorkspaceTrusted', false, localize('workspaceTrustedCtx', "Whether the current workspace has been trusted by the user."))
+};
+
+export class WorkspaceTrustContextKeys extends Disposable implements IWorkbenchContribution {
+
+ private readonly _ctxWorkspaceTrustEnabled: IContextKey;
+ private readonly _ctxWorkspaceTrustState: IContextKey;
+
+ constructor(
+ @IContextKeyService contextKeyService: IContextKeyService,
+ @IWorkspaceTrustEnablementService workspaceTrustEnablementService: IWorkspaceTrustEnablementService,
+ @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService
+ ) {
+ super();
+
+ this._ctxWorkspaceTrustState = WorkspaceTrustContext.IsTrusted.bindTo(contextKeyService);
+ this._ctxWorkspaceTrustEnabled = WorkspaceTrustContext.IsEnabled.bindTo(contextKeyService);
+ this._ctxWorkspaceTrustEnabled.set(workspaceTrustEnablementService.isWorkspaceTrustEnabled());
+
+ this._register(workspaceTrustManagementService.onDidChangeTrust(trusted => this._ctxWorkspaceTrustState.set(trusted)));
+ }
+}
+
+Registry.as