mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Merge branch 'main' into joh/webpack5
This commit is contained in:
commit
d38279ba73
|
@ -16,3 +16,4 @@
|
|||
**/extensions/markdown-language-features/notebook-out/**
|
||||
**/extensions/typescript-basics/test/colorize-fixtures/**
|
||||
**/extensions/**/dist/**
|
||||
**/extensions/typescript-language-features/test-workspace/**
|
||||
|
|
|
@ -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": [
|
||||
|
|
32
.github/classifier.json
vendored
32
.github/classifier.json
vendored
|
@ -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": []},
|
||||
|
|
|
@ -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'
|
||||
|
|
1
.github/workflows/devcontainer-cache.yml
vendored
1
.github/workflows/devcontainer-cache.yml
vendored
|
@ -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
1
.gitignore
vendored
|
@ -16,3 +16,4 @@ test-results/
|
|||
yarn-error.log
|
||||
vscode.lsif
|
||||
vscode.db
|
||||
/.profile-oss
|
||||
|
|
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
|
@ -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"
|
||||
|
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -4,6 +4,7 @@
|
|||
"files.exclude": {
|
||||
".git": true,
|
||||
".build": true,
|
||||
".profile-oss": true,
|
||||
"**/.DS_Store": true,
|
||||
"build/**/*.js": {
|
||||
"when": "$(basename).ts"
|
||||
|
|
17
build/azure-pipelines/common/sign-win32.ts
Normal file
17
build/azure-pipelines/common/sign-win32.ts
Normal 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])
|
||||
]);
|
84
build/azure-pipelines/common/sign.ts
Normal file
84
build/azure-pipelines/common/sign.ts
Normal 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));
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.ESRPClient" version="1.2.47" />
|
||||
</packages>
|
|
@ -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"
|
||||
|
|
|
@ -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: |
|
||||
|
|
|
@ -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
|
|
@ -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'] })
|
||||
|
|
|
@ -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"
|
||||
|
|
170
build/yarn.lock
170
build/yarn.lock
|
@ -186,6 +186,13 @@
|
|||
"@types/events" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cssnano@^4.0.0":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.1.tgz#67fa912753d80973a016e7684a47fedf338aacff"
|
||||
integrity sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q==
|
||||
dependencies:
|
||||
postcss "5 - 7"
|
||||
|
||||
"@types/debounce@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7"
|
||||
|
@ -286,6 +293,14 @@
|
|||
"@types/js-beautify" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/gulp-postcss@^8.0.0":
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/gulp-postcss/-/gulp-postcss-8.0.0.tgz#f7e86d45e4999fd43e6d8c55b00504c88a67ad61"
|
||||
integrity sha512-AVgjA03bpkYONKZpzuJviB9PzaNbDzrovYPbenj8/XxivUc35C/dIzJanyaQv7CFqfLLPLsqSalmtP3GLq6iag==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/vinyl" "*"
|
||||
|
||||
"@types/gulp-rename@^0.0.33":
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/gulp-rename/-/gulp-rename-0.0.33.tgz#38d146e97786569f74f5391a1b1f9b5198674b6c"
|
||||
|
@ -428,6 +443,16 @@
|
|||
"@types/glob" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/source-list-map@*":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
|
||||
integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
|
||||
|
||||
"@types/tapable@^1":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
|
||||
integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==
|
||||
|
||||
"@types/through2@^2.0.34":
|
||||
version "2.0.34"
|
||||
resolved "https://registry.yarnpkg.com/@types/through2/-/through2-2.0.34.tgz#9c2a259a238dace2a05a2f8e94b786961bc27ac4"
|
||||
|
@ -442,6 +467,11 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/tmp@^0.2.1":
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.1.tgz#83ecf4ec22a8c218c71db25f316619fe5b986011"
|
||||
integrity sha512-7cTXwKP/HLOPVgjg+YhBdQ7bMiobGMuoBmrGmqwIWJv8elC6t1DfVc/mn4fD9UE1IjhwmhaQ5pGVXkmXbH0rhg==
|
||||
|
||||
"@types/tough-cookie@*":
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.2.tgz#e0d481d8bb282ad8a8c9e100ceb72c995fb5e709"
|
||||
|
@ -454,6 +484,13 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/uglify-js@*":
|
||||
version "3.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea"
|
||||
integrity sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==
|
||||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@types/underscore@^1.8.9":
|
||||
version "1.8.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.9.tgz#fef41f800cd23db1b4f262ddefe49cd952d82323"
|
||||
|
@ -489,6 +526,27 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/webpack-sources@*":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.1.tgz#6af17e3a3ded71eec2b98008d7c12f498a0a4506"
|
||||
integrity sha512-MjM1R6iuw8XaVbtkCBz0N349cyqBjJHCbQiOeppe3VBeFvxqs74RKHAVt9LkxTnUWc7YLZOEsUfPUnmK6SBPKQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/source-list-map" "*"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@types/webpack@^4.41.25":
|
||||
version "4.41.30"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.30.tgz#fd3db6d0d41e145a8eeeafcd3c4a7ccde9068ddc"
|
||||
integrity sha512-GUHyY+pfuQ6haAfzu4S14F+R5iGRwN6b2FRNJY7U0NilmFAqbsOfK6j1HwuLBAqwRIT+pVdNDJGJ6e8rpp0KHA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/tapable" "^1"
|
||||
"@types/uglify-js" "*"
|
||||
"@types/webpack-sources" "*"
|
||||
anymatch "^3.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
"@types/xml2js@0.0.33":
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.0.33.tgz#20c5dd6460245284d64a55690015b95e409fb7de"
|
||||
|
@ -574,6 +632,21 @@ ajv@^6.12.3:
|
|||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
anymatch@^3.0.0:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
applicationinsights@1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
|
||||
|
@ -747,6 +820,15 @@ caseless@~0.12.0:
|
|||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
cheerio@^1.0.0-rc.1:
|
||||
version "1.0.0-rc.2"
|
||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
|
||||
|
@ -771,6 +853,18 @@ clone-response@^1.0.2:
|
|||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
colors@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
||||
|
@ -997,6 +1091,11 @@ esbuild@^0.12.6:
|
|||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.6.tgz#85bc755c7cf3005d4f34b4f10f98049ce0ee67ce"
|
||||
integrity sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA==
|
||||
|
||||
escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
eslint-scope@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
|
||||
|
@ -1130,6 +1229,18 @@ glob@^7.0.6:
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.1.3:
|
||||
version "7.1.7"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
|
||||
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
|
@ -1182,6 +1293,11 @@ har-validator@~5.1.3:
|
|||
ajv "^6.12.3"
|
||||
har-schema "^2.0.0"
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
hash-base@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
|
||||
|
@ -1487,6 +1603,11 @@ node-fetch@^2.6.0:
|
|||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
normalize-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
normalize-url@^4.1.0:
|
||||
version "4.5.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
|
||||
|
@ -1575,6 +1696,11 @@ performance-now@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||
|
||||
picomatch@^2.0.4:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
|
||||
|
||||
plist@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c"
|
||||
|
@ -1584,6 +1710,15 @@ plist@^3.0.1:
|
|||
xmlbuilder "^9.0.7"
|
||||
xmldom "0.1.x"
|
||||
|
||||
"postcss@5 - 7":
|
||||
version "7.0.36"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb"
|
||||
integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^6.1.0"
|
||||
|
||||
priorityqueuejs@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8"
|
||||
|
@ -1707,6 +1842,13 @@ responselike@^2.0.0:
|
|||
dependencies:
|
||||
lowercase-keys "^2.0.0"
|
||||
|
||||
rimraf@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
|
@ -1766,11 +1908,16 @@ shebang-regex@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
source-map@0.6.1:
|
||||
source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.7.3:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
|
@ -1803,6 +1950,20 @@ string_decoder@~0.10.x:
|
|||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
tmp@0.0.29:
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0"
|
||||
|
@ -1810,6 +1971,13 @@ tmp@0.0.29:
|
|||
dependencies:
|
||||
os-tmpdir "~1.0.1"
|
||||
|
||||
tmp@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
|
||||
integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
|
||||
dependencies:
|
||||
rimraf "^3.0.0"
|
||||
|
||||
tough-cookie@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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==
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1544,6 +1544,7 @@ export interface AuthenticationSession {
|
|||
id: string;
|
||||
}
|
||||
scopes: ReadonlyArray<string>;
|
||||
idToken?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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[],
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}; }`);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()]) ()' });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
4
src/vs/monaco.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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']; }
|
||||
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -225,7 +225,7 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
|
|||
'default': true,
|
||||
'restricted': true,
|
||||
'scope': ConfigurationScope.APPLICATION,
|
||||
'tags': ['usesOnlineServices']
|
||||
'tags': ['usesOnlineServices', 'telemetry']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 };
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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() {
|
||||
|
|
25
src/vs/workbench/contrib/list/browser/list.contribution.ts
Normal file
25
src/vs/workbench/contrib/list/browser/list.contribution.ts
Normal 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);
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -172,3 +172,7 @@
|
|||
.xterm-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.xterm-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue