Merge branch 'main' into joh/webpack5

This commit is contained in:
Martin Aeschlimann 2021-07-13 09:27:13 +02:00
commit d38279ba73
No known key found for this signature in database
GPG key ID: 2609A01E695523E3
132 changed files with 2581 additions and 1544 deletions

View file

@ -16,3 +16,4 @@
**/extensions/markdown-language-features/notebook-out/**
**/extensions/typescript-basics/test/colorize-fixtures/**
**/extensions/**/dist/**
**/extensions/typescript-language-features/test-workspace/**

View file

@ -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": [

View file

@ -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": []},

View file

@ -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'

View file

@ -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:

1
.gitignore vendored
View file

@ -16,3 +16,4 @@ test-results/
yarn-error.log
vscode.lsif
vscode.db
/.profile-oss

8
.vscode/launch.json vendored
View file

@ -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"

View file

@ -4,6 +4,7 @@
"files.exclude": {
".git": true,
".build": true,
".profile-oss": true,
"**/.DS_Store": true,
"build/**/*.js": {
"when": "$(basename).ts"

View file

@ -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])
]);

View file

@ -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));
}

View file

@ -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

View file

@ -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'))

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="ESRP" value="https://microsoft.pkgs.visualstudio.com/_packaging/ESRP/nuget/v3/index.json" />
</packageSources>
<disabledPackageSources>
<clear />
</disabledPackageSources>
</configuration>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.ESRPClient" version="1.2.47" />
</packages>

View file

@ -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"

View file

@ -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: |

View file

@ -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

View file

@ -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'] })

View file

@ -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"

View file

@ -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"

View file

@ -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",

View file

@ -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();

View file

@ -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"
}
}
}

View file

@ -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"
},

View file

@ -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"
}
}

View file

@ -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==

View file

@ -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"

View file

@ -281,7 +281,7 @@ class KeyboardController<T> 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<T> implements ISpliceable<T>, IThemable, IDisposable {
this.ariaLabel = this.accessibilityProvider.getWidgetAriaLabel();
}
if (this._options.multipleSelectionSupport) {
if (this._options.multipleSelectionSupport !== false) {
this.view.domNode.setAttribute('aria-multiselectable', 'true');
}
}

View file

@ -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();
});
});

View file

@ -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}`);
}
}
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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;
}
/**

View file

@ -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;
}
}

View file

@ -1544,6 +1544,7 @@ export interface AuthenticationSession {
id: string;
}
scopes: ReadonlyArray<string>;
idToken?: string;
}
/**

View file

@ -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 {

View file

@ -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<ViewLineData | null>;
@ -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
);
}
}

View file

@ -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();

View file

@ -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[],
) {
}

View file

@ -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;

View file

@ -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<InsertedInlineText>();
const additionalLines = new Array<LineData>();
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<IModelDeltaDecoration>(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}; }`);
}

View file

@ -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;

View file

@ -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()]) ()' });
});
});

View file

@ -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();
}

View file

@ -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;

View file

@ -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),
]);
});
});

4
src/vs/monaco.d.ts vendored
View file

@ -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;
}
/**

View file

@ -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']; }

View file

@ -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<ILocalExtension>;
waitUntilTaskIsFinished(): Promise<ILocalExtension>;
cancel(): void;
}
export type UninstallExtensionTaskOptions = { readonly remove?: boolean; readonly versionOnly?: boolean };
export interface IUninstallExtensionTask {
readonly extension: ILocalExtension;
run(): Promise<void>;
waitUntilTaskIsFinished(): Promise<void>;
cancel(): void;
}
export abstract class AbstractExtensionManagementService extends Disposable implements IExtensionManagementService {
declare readonly _serviceBrand: undefined;
private reportedExtensions: Promise<IReportedExtension[]> | undefined;
private lastReportTimestamp = 0;
private readonly installingExtensions = new Map<string, IInstallExtensionTask>();
private readonly uninstallingExtensions = new Map<string, IUninstallExtensionTask>();
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
readonly onInstallExtension: Event<InstallExtensionEvent> = this._onInstallExtension.event;
protected readonly _onDidInstallExtensions = this._register(new Emitter<InstallExtensionResult[]>());
readonly onDidInstallExtensions = this._onDidInstallExtensions.event;
protected readonly _onUninstallExtension = this._register(new Emitter<IExtensionIdentifier>());
readonly onUninstallExtension: Event<IExtensionIdentifier> = this._onUninstallExtension.event;
protected _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionEvent>());
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = 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<ILocalExtension> {
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<void> {
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
return this.unininstallExtension(extension, options);
}
async reinstallFromGallery(extension: ILocalExtension): Promise<void> {
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<IReportedExtension[]> {
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<ILocalExtension> {
// 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<string, { task: IInstallExtensionTask, manifest: IExtensionManifest }>());
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<any[]>((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 && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>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<void> => {
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<IGalleryExtension> {
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<boolean> {
const report = await this.getExtensionsReport();
return getMaliciousExtensionsSet(report).has(extension.identifier.id);
}
private async unininstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise<void> {
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<any[]>((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<IGalleryExtension> {
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<IGalleryExtension> {
const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None);
return galleryResult.firstPage[0];
}
private async findGalleryExtensionByName(name: string): Promise<IGalleryExtension> {
const galleryResult = await this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None);
return galleryResult.firstPage[0];
}
private async updateReportCache(): Promise<IReportedExtension[]> {
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<URI>;
abstract unzip(zipLocation: URI): Promise<IExtensionIdentifier>;
abstract getManifest(vsix: URI): Promise<IExtensionManifest>;
abstract install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
abstract canInstall(extension: IGalleryExtension): Promise<boolean>;
abstract getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>;
abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
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 | string>)): Error {
const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
if (errors.length === 1) {
return errors[0] instanceof Error ? <Error>errors[0] : new Error(<string>errors[0]);
}
return errors.reduce<Error>((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<T> {
private readonly barrier = new Barrier();
private cancellablePromise: CancelablePromise<T> | undefined;
async waitUntilTaskIsFinished(): Promise<T> {
await this.barrier.wait();
return this.cancellablePromise!;
}
async run(): Promise<T> {
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<T>;
}

View file

@ -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;
}

View file

@ -98,12 +98,20 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
private readonly channel: IChannel,
) {
super();
this._register(this.channel.listen<InstallExtensionEvent>('onInstallExtension')(e => this._onInstallExtension.fire(e)));
this._register(this.channel.listen<readonly InstallExtensionResult[]>('onDidInstallExtensions')(results => this._onDidInstallExtensions.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local })))));
this._register(this.channel.listen<InstallExtensionEvent>('onInstallExtension')(e => this._onInstallExtension.fire({ identifier: e.identifier, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source })));
this._register(this.channel.listen<readonly InstallExtensionResult[]>('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<IExtensionIdentifier>('onUninstallExtension')(e => this._onUninstallExtension.fire(e)));
this._register(this.channel.listen<DidUninstallExtensionEvent>('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire(e)));
}
private isUriComponents(thing: unknown): thing is UriComponents {
if (!thing) {
return false;
}
return typeof (<any>thing).path === 'string' &&
typeof (<any>thing).scheme === 'string';
}
zip(extension: ILocalExtension): Promise<URI> {
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(<UriComponents>result)));
}

View file

@ -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<ILocalExtension>;
waitUntilTaskIsFinished(): Promise<ILocalExtension>;
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<IReportedExtension[]> | undefined;
private lastReportTimestamp = 0;
private readonly installingExtensions = new Map<string, InstallExtensionTask>();
private readonly uninstallingExtensions: Map<string, CancelablePromise<void>> = new Map<string, CancelablePromise<void>>();
private readonly manifestCache: ExtensionsManifestCache;
private readonly extensionsDownloader: ExtensionsDownloader;
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
readonly onInstallExtension: Event<InstallExtensionEvent> = this._onInstallExtension.event;
private readonly _onDidInstallExtensions = this._register(new Emitter<InstallExtensionResult[]>());
readonly onDidInstallExtensions = this._onDidInstallExtensions.event;
private readonly _onUninstallExtension = this._register(new Emitter<IExtensionIdentifier>());
readonly onUninstallExtension: Event<IExtensionIdentifier> = this._onUninstallExtension.event;
private _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionEvent>());
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = 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<URI> {
@ -148,6 +100,65 @@ export class ExtensionManagementService extends Disposable implements IExtension
return getManifest(zipPath);
}
getInstalled(type: ExtensionType | null = null): Promise<ILocalExtension[]> {
return this.extensionsScanner.scanExtensions(type);
}
async canInstall(extension: IGalleryExtension): Promise<boolean> {
return true;
}
async install(vsix: URI, options: InstallVSIXOptions = {}): Promise<ILocalExtension> {
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<ILocalExtension> {
this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id);
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((<ILocalExtensionManifest>local.manifest).__metadata || {}), ...metadata });
this.manifestCache.invalidate();
return local;
}
async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension> {
this.logService.trace('ExtensionManagementService#updateExtensionScope', local.identifier.id);
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((<ILocalExtensionManifest>local.manifest).__metadata || {}), isMachineScoped });
this.manifestCache.invalidate();
return local;
}
removeDeprecatedExtensions(): Promise<void> {
return this.extensionsScanner.cleanUp();
}
private async downloadVsix(vsix: URI): Promise<URI> {
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<IFile[]> {
const collectFilesFromDirectory = async (dir: string): Promise<string[]> => {
@ -173,588 +184,20 @@ export class ExtensionManagementService extends Disposable implements IExtension
return files.map(f => (<IFile>{ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f }));
}
async canInstall(extension: IGalleryExtension): Promise<boolean> {
return true;
}
async install(vsix: URI, options: InstallVSIXOptions = {}): Promise<ILocalExtension> {
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<ILocalExtension> {
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<URI> {
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<ILocalExtension> {
// 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<string, { task: InstallExtensionTask, manifest: IExtensionManifest }>());
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<any[]>((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 && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>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<void> => {
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<IGalleryExtension> {
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<void> {
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<boolean> {
const report = await this.getExtensionsReport();
return getMaliciousExtensionsSet(report).has(extension.identifier.id);
}
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
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<ILocalExtension> {
this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id);
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((<ILocalExtensionManifest>local.manifest).__metadata || {}), ...metadata });
this.manifestCache.invalidate();
return local;
}
async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension> {
this.logService.trace('ExtensionManagementService#updateExtensionScope', local.identifier.id);
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((<ILocalExtensionManifest>local.manifest).__metadata || {}), isMachineScoped });
this.manifestCache.invalidate();
return local;
}
private async findGalleryExtension(local: ILocalExtension): Promise<IGalleryExtension> {
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<IGalleryExtension> {
const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None);
return galleryResult.firstPage[0];
}
private async findGalleryExtensionByName(name: string): Promise<IGalleryExtension> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<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);
// 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<ILocalExtension[]> {
return this.extensionsScanner.scanExtensions(type);
}
removeDeprecatedExtensions(): Promise<void> {
return this.extensionsScanner.cleanUp();
}
getExtensionsReport(): Promise<IReportedExtension[]> {
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<IReportedExtension[]> {
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 | string>)): Error {
const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
if (errors.length === 1) {
return errors[0] instanceof Error ? <Error>errors[0] : new Error(<string>errors[0]);
}
return errors.reduce<Error>((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<ILocalExtension> | undefined;
abstract class AbstractInstallExtensionTask extends AbstractExtensionTask<ILocalExtension> 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<ILocalExtension> {
await this.barrier.wait();
return this.cancellablePromise!;
}
async run(): Promise<ILocalExtension> {
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<ILocalExtension> {
@ -801,7 +244,6 @@ abstract class AbstractInstallExtensionTask implements InstallExtensionTask {
return local;
}
abstract install(token: CancellationToken): Promise<ILocalExtension>;
}
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<ILocalExtension> {
return this.installGalleryExtension(this.gallery, this.options, token);
}
private async installGalleryExtension(gallery: IGalleryExtension, options: InstallOptions, token: CancellationToken): Promise<ILocalExtension> {
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<ILocalExtension> {
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<Required<InstallableExtension>> {
@ -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<ILocalExtension> {
protected async doRun(token: CancellationToken): Promise<ILocalExtension> {
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<IMetadata> {
@ -936,3 +362,41 @@ class InstallVSIXTask extends AbstractInstallExtensionTask {
return {};
}
}
class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask {
constructor(
readonly extension: ILocalExtension,
private readonly options: UninstallExtensionTaskOptions,
private readonly extensionsScanner: ExtensionsScanner
) { super(); }
protected async doRun(token: CancellationToken): Promise<void> {
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)));
}
}
}
}
}

View file

@ -110,10 +110,7 @@ export const WorkbenchListHasSelectionOrFocus = new RawContextKey<boolean>('list
export const WorkbenchListDoubleSelection = new RawContextKey<boolean>('listDoubleSelection', false);
export const WorkbenchListMultiSelection = new RawContextKey<boolean>('listMultiSelection', false);
export const WorkbenchListSelectionNavigation = new RawContextKey<boolean>('listSelectionNavigation', false);
export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey<boolean>('listSupportsKeyboardNavigation', true);
export const WorkbenchListAutomaticKeyboardNavigationKey = 'listAutomaticKeyboardNavigation';
export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey<boolean>(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<T> extends List<T> {
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<T> extends PagedList<T> {
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<TRow> extends Table<TRow> {
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<T, TFilterData, TOptions extends IAbstractTre
keybindingService: IKeybindingService,
accessibilityService: IAccessibilityService,
): { options: TOptions, getAutomaticKeyboardNavigation: () => 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<TInput, T, TFilterData> {
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<TInput, T, TFilterData> {
newOptions = { ...newOptions, renderIndentGuides };
}
if (e.affectsConfiguration(listSmoothScrolling)) {
const smoothScrolling = Boolean(configurationService.getValue<boolean>(listSmoothScrolling));
const smoothScrolling = Boolean(!!configurationService.getValue(listSmoothScrolling));
newOptions = { ...newOptions, smoothScrolling };
}
if (e.affectsConfiguration(keyboardNavigationSettingKey)) {
@ -1166,7 +1156,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
newOptions = { ...newOptions, automaticKeyboardNavigation: getAutomaticKeyboardNavigation() };
}
if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) {
const horizontalScrolling = Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
const horizontalScrolling = Boolean(!!configurationService.getValue(horizontalScrollingKey));
newOptions = { ...newOptions, horizontalScrolling };
}
if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {

View file

@ -225,7 +225,7 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
'default': true,
'restricted': true,
'scope': ConfigurationScope.APPLICATION,
'tags': ['usesOnlineServices']
'tags': ['usesOnlineServices', 'telemetry']
}
}
});

View file

@ -37,7 +37,7 @@ export function detectAvailableProfiles(
includeDetectedProfiles,
fsProvider,
logService,
configurationService.getValue<boolean>(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<string>(TerminalSettingId.DefaultProfileWindows),
testPwshSourcePaths,

View file

@ -149,7 +149,7 @@ export class Win32UpdateService extends AbstractUpdateService {
.then(() => updatePackagePath);
});
}).then(packagePath => {
const fastUpdatesEnabled = this.configurationService.getValue<boolean>('update.enableWindowsBackgroundUpdates');
const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates');
this.availableUpdate = { packagePath };

View file

@ -331,15 +331,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
private syncKeybindingsPerPlatform(): boolean {
let userValue = this.configurationService.inspect<boolean>('settingsSync.keybindingsPerPlatform').userValue;
let userValue = !!this.configurationService.inspect('settingsSync.keybindingsPerPlatform').userValue;
if (userValue !== undefined) {
return userValue;
}
userValue = this.configurationService.inspect<boolean>('sync.keybindingsPerPlatform').userValue;
userValue = !!this.configurationService.inspect('sync.keybindingsPerPlatform').userValue;
if (userValue !== undefined) {
return userValue;
}
return this.configurationService.getValue<boolean>('settingsSync.keybindingsPerPlatform');
return !!this.configurationService.getValue('settingsSync.keybindingsPerPlatform');
}
}

View file

@ -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 {

View file

@ -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<string, IDisposable>();
private readonly testProviderRegistrations = new Map<string, IDisposable>();
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<T>(runId: string, fn: (run: LiveTestResult) => T): T | undefined {

View file

@ -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();
}
});
};
}

View file

@ -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

View file

@ -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, {

View file

@ -207,7 +207,7 @@ class ToggleScreencastModeAction extends Action2 {
const event = new StandardKeyboardEvent(e);
const shortcut = keybindingService.softDispatch(event, event.target);
if (shortcut || !configurationService.getValue<boolean>('screencastMode.onlyKeyboardShortcuts')) {
if (shortcut || !configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) {
if (
event.ctrlKey || event.altKey || event.metaKey || event.shiftKey
|| length > 20

View file

@ -394,13 +394,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (!this.state.zenMode.active) {
// Statusbar visibility
const newStatusbarHiddenValue = !this.configurationService.getValue<boolean>(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<boolean>(Settings.ACTIVITYBAR_VISIBLE);
const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE);
if (newActivityBarHiddenValue !== this.state.activityBar.hidden) {
this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout);
}

View file

@ -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;

View file

@ -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<void>;
}
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");

View file

@ -352,14 +352,14 @@ function registerDiffEditorCommands(): void {
function toggleDiffSideBySide(accessor: ServicesAccessor): void {
const configurationService = accessor.get(IConfigurationService);
const newValue = !configurationService.getValue<boolean>('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<boolean>('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<boolean>('workbench.editor.enablePreview');
const currentSetting = configurationService.getValue('workbench.editor.enablePreview');
const newSetting = currentSetting === true ? false : true;
configurationService.updateValue('workbench.editor.enablePreview', newSetting);
}

View file

@ -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<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, configurationService: ITextResourceConfigurationService, key: string, limit: number = 10): IEditorMemento<T> {
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<T> {
[group: number]: T;
[group: GroupIdentifier]: T;
}
export class EditorMemento<T> implements IEditorMemento<T> {
export class EditorMemento<T> extends Disposable implements IEditorMemento<T> {
private static readonly SHARED_EDITOR_STATE = -1; // pick a number < 0 to be outside group id range
private cache: LRUCache<string, MapGroupToMemento<T>> | undefined;
private cleanedUp = false;
private editorDisposables: Map<EditorInput, IDisposable> | 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<T> implements IEditorMemento<T> {
const cache = this.doLoad();
let mementoForResource = cache.get(resource.toString());
if (!mementoForResource) {
mementoForResource = Object.create(null) as MapGroupToMemento<T>;
cache.set(resource.toString(), mementoForResource);
// Ensure mementos for resource map
let mementosForResource = cache.get(resource.toString());
if (!mementosForResource) {
mementosForResource = Object.create(null) as MapGroupToMemento<T>;
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<T> implements IEditorMemento<T> {
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<T> implements IEditorMemento<T> {
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<T> implements IEditorMemento<T> {
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<T> implements IEditorMemento<T> {
}
private doGetResource(resourceOrEditor: URI | EditorInput): URI | undefined {
if (resourceOrEditor instanceof EditorInput) {
if (isEditorInput(resourceOrEditor)) {
return resourceOrEditor.resource;
}
@ -354,12 +394,16 @@ export class EditorMemento<T> implements IEditorMemento<T> {
// 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);
}
}

View file

@ -68,7 +68,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
) {
super(id, telemetryService, themeService, storageService);
this.editorMemento = this.getEditorMemento<IEditorViewState>(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this.editorMemento = this.getEditorMemento<IEditorViewState>(editorGroupService, textResourceConfigurationService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this._register(this.textResourceConfigurationService.onDidChangeConfiguration(() => {
const resource = this.getActiveResource();

View file

@ -194,10 +194,15 @@ const registry = Registry.as<IConfigurationRegistry>(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,

View file

@ -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 {

View file

@ -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<void>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;

View file

@ -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') {

View file

@ -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) {

View file

@ -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<T> {
@ -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) {

View file

@ -275,7 +275,14 @@ CommandsRegistry.registerCommand({
async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, labelService: ILabelService, configurationService: IConfigurationService): Promise<void> {
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);

View file

@ -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'
}
}
});

View file

@ -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() {

View file

@ -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<boolean>('listSupportsKeyboardNavigation', true);
export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey<boolean>(WorkbenchListAutomaticKeyboardNavigationKey, true);
export class ListContext implements IWorkbenchContribution {
constructor(
@IContextKeyService contextKeyService: IContextKeyService
) {
WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService);
WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService);
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ListContext, LifecyclePhase.Starting);

View file

@ -177,7 +177,7 @@ class NotebookOutlineRenderer implements ITreeRenderer<OutlineEntry, FuzzyScore,
template.container.style.removeProperty('--outline-element-color');
template.decoration.innerText = '';
if (markerInfo) {
const useBadges = this._configurationService.getValue<boolean>(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<OutlineEntry, FuzzyScore,
template.decoration.innerText = markerInfo.count > 9 ? '9+' : String(markerInfo.count);
}
const color = this._themeService.getColorTheme().getColor(markerInfo.topSev === MarkerSeverity.Error ? listErrorForeground : listWarningForeground);
const useColors = this._configurationService.getValue<boolean>(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');

View file

@ -230,12 +230,12 @@ class ToggleRenderAction extends Action2 {
const configurationService = accessor.get(IConfigurationService);
if (this.toggleOutputs !== undefined) {
const oldValue = configurationService.getValue<boolean>('notebook.diff.ignoreOutputs');
const oldValue = configurationService.getValue('notebook.diff.ignoreOutputs');
configurationService.updateValue('notebook.diff.ignoreOutputs', !oldValue);
}
if (this.toggleMetadata !== undefined) {
const oldValue = configurationService.getValue<boolean>('notebook.diff.ignoreMetadata');
const oldValue = configurationService.getValue('notebook.diff.ignoreMetadata');
configurationService.updateValue('notebook.diff.ignoreMetadata', !oldValue);
}
}

View file

@ -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<INotebookEditorViewState>(_editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
this._editorMemento = this.getEditorMemento<INotebookEditorViewState>(_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)));

View file

@ -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<string, EditorMemento<unknown>>();
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<string, INotebookEditorContribution>();
private _scrollBeyondLastLine: boolean;
private readonly _memento: Memento;
private readonly _onDidFocusEmitter = this._register(new Emitter<void>());
public readonly onDidFocus = this._onDidFocusEmitter.event;
private readonly _onDidBlurEmitter = this._register(new Emitter<void>());
@ -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<boolean>('editor.scrollBeyondLastLine');
@ -526,23 +518,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
//#region Editor Core
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
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<T>;
}
protected getMemento(scope: StorageScope): MementoObject {
return this._memento.getMemento(scope, StorageTarget.MACHINE);
}
private _updateForNotebookConfiguration() {
if (!this._overlayContainer) {
return;

View file

@ -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({

View file

@ -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<string, DOM.IFocusTracker>();
this.editorMemento = this.getEditorMemento<ISettingsEditor2State>(editorGroupService, SETTINGS_EDITOR_STATE_KEY);
this.editorMemento = this.getEditorMemento<ISettingsEditor2State>(editorGroupService, textResourceConfigurationService, SETTINGS_EDITOR_STATE_KEY);
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.source !== ConfigurationTarget.DEFAULT) {

View file

@ -2885,7 +2885,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return Promise.resolve(undefined);
}
content = pickTemplateResult.content;
let editorConfig = this.configurationService.getValue<any>();
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));
}

View file

@ -55,7 +55,8 @@ export class TaskQuickPick extends Disposable {
}
private showDetail(): boolean {
return this.configurationService.getValue<boolean>(QUICKOPEN_DETAIL_CONFIG);
// Ensure invalid values get converted into boolean values
return !!this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG);
}
private guessTaskLabel(task: Task | ConfiguringTask): string {

View file

@ -62,6 +62,7 @@ interface ActiveTerminalData {
terminal: ITerminalInstance;
task: Task;
promise: Promise<ITaskSummary>;
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<TaskTerminateResponse> {
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<ITaskSummary> {
return new Promise<ITaskSummary>(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<ITaskSummary> {
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<string>, alreadyResolved?: Map<string, string>): Promise<ITaskSummary> {
// 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<ITaskSummary>(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<string>, alreadyResolved: Map<string, string>): Promise<ResolvedVariables | undefined> {
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 = {

View file

@ -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<number>('workbench.hover.delay'));
}, this._configurationService.getValue('workbench.hover.delay'));
this.add(this._tooltipScheduler);
this._tooltipScheduler.schedule();
}

View file

@ -172,3 +172,7 @@
.xterm-underline {
text-decoration: underline;
}
.xterm-strikethrough {
text-decoration: line-through;
}

View file

@ -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);
}
});

View file

@ -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);

View file

@ -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);
}

View file

@ -1274,7 +1274,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
}
if (syncResourceConflicts[1].some(({ remoteResource }) => isEqual(remoteResource, model.uri))) {
return this.configurationService.getValue<boolean>('diffEditor.renderSideBySide');
return this.configurationService.getValue('diffEditor.renderSideBySide');
}
return false;

View file

@ -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;
}

View file

@ -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'));
}

View file

@ -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<IWalkThroughEditorViewState>(editorGroupService, WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY);
this.editorMemento = this.getEditorMemento<IWalkThroughEditorViewState>(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<number>('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<string>('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) => {

View file

@ -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<boolean>('isWorkspaceTrustEnabled', false, localize('workspaceTrustEnabledCtx', "Whether the workspace trust feature is enabled.")),
IsTrusted: new RawContextKey<boolean>('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<boolean>;
private readonly _ctxWorkspaceTrustState: IContextKey<boolean>;
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<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustContextKeys, LifecyclePhase.Restored);
/*
* Trust Request via Service UX handler
*/

View file

@ -233,7 +233,7 @@ import { EditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/c
'type': 'boolean',
'description': localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to a Microsoft online service. \nThis option requires restart to take effect."),
'default': true,
'tags': ['usesOnlineServices']
'tags': ['usesOnlineServices', 'telemetry']
}
}
});

View file

@ -40,7 +40,7 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/
import { coalesce } from 'vs/base/common/arrays';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { assertIsDefined } from 'vs/base/common/types';
import { assertIsDefined, isArray } from 'vs/base/common/types';
import { IOpenerService, OpenOptions } from 'vs/platform/opener/common/opener';
import { Schemas } from 'vs/base/common/network';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
@ -360,7 +360,7 @@ export class NativeWindow extends Disposable {
// or setting is disabled. Also enabled when running with --wait from the command line.
const visibleEditorPanes = this.editorService.visibleEditorPanes;
if (visibleEditorPanes.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) {
const closeWhenEmpty = this.configurationService.getValue<boolean>('window.closeWhenEmpty');
const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty');
if (closeWhenEmpty || this.environmentService.args.wait) {
this.closeEmptyWindowScheduler.schedule();
}
@ -564,8 +564,9 @@ export class NativeWindow extends Disposable {
const actions: Array<MenuItemAction | Separator> = [];
const disabled = this.configurationService.getValue<boolean>('keyboard.touchbar.enabled') === false;
const ignoredItems = this.configurationService.getValue<string[]>('keyboard.touchbar.ignored') || [];
const disabled = this.configurationService.getValue('keyboard.touchbar.enabled') === false;
const touchbarIgnored = this.configurationService.getValue('keyboard.touchbar.ignored');
const ignoredItems = isArray(touchbarIgnored) ? touchbarIgnored : [];
// Fill actions into groups respecting order
this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions));

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