mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Merge branch 'main' into joh/rewrite-privates
This commit is contained in:
commit
58e979f39e
|
@ -8,7 +8,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- task: UseDotNet@2
|
||||
|
@ -20,16 +20,16 @@ steps:
|
|||
displayName: "Use ESRP client"
|
||||
|
||||
- ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download artifacts
|
||||
inputs:
|
||||
artifact: ${{ target }}
|
||||
path: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download artifacts
|
||||
inputs:
|
||||
artifact: ${{ target }}
|
||||
path: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}
|
||||
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip
|
||||
destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }}
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip
|
||||
destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }}
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
|
@ -49,17 +49,17 @@ steps:
|
|||
displayName: "Code sign"
|
||||
|
||||
- ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}:
|
||||
- powershell: |
|
||||
$ASSET_ID = "${{ target }}".replace("unsigned_", "");
|
||||
echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID"
|
||||
displayName: Set asset id variable
|
||||
- powershell: |
|
||||
$ASSET_ID = "${{ target }}".replace("unsigned_", "");
|
||||
echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID"
|
||||
displayName: Set asset id variable
|
||||
|
||||
- task: ArchiveFiles@2
|
||||
inputs:
|
||||
rootFolderOrFile: $(Build.ArtifactStagingDirectory)/sign/${{ target }}/code.exe
|
||||
includeRootFolder: false
|
||||
archiveType: zip
|
||||
archiveFile: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip
|
||||
- task: ArchiveFiles@2
|
||||
inputs:
|
||||
rootFolderOrFile: $(Build.ArtifactStagingDirectory)/sign/${{ target }}/code.exe
|
||||
includeRootFolder: false
|
||||
archiveType: zip
|
||||
archiveFile: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip
|
||||
|
||||
- publish: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip
|
||||
artifact: $(ASSET_ID)
|
||||
- publish: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip
|
||||
artifact: $(ASSET_ID)
|
||||
|
|
|
@ -13,7 +13,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- script: |
|
||||
|
|
|
@ -7,7 +7,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- script: |
|
||||
|
|
|
@ -7,7 +7,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key"
|
||||
|
||||
- script: |
|
||||
|
|
|
@ -27,7 +27,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key"
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
|
|
|
@ -15,7 +15,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- script: |
|
||||
|
|
|
@ -13,7 +13,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- script: |
|
||||
|
|
|
@ -7,7 +7,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
|
|
|
@ -27,7 +27,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
|
|
|
@ -12,7 +12,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
|
|
|
@ -4,37 +4,37 @@ parameters:
|
|||
|
||||
steps:
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- task: AzureKeyVault@1
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
- task: AzureKeyVault@1
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
cat << EOF > ~/.netrc
|
||||
machine github.com
|
||||
login vscode
|
||||
password $(github-distro-mixin-password)
|
||||
EOF
|
||||
- script: |
|
||||
set -e
|
||||
cat << EOF > ~/.netrc
|
||||
machine github.com
|
||||
login vscode
|
||||
password $(github-distro-mixin-password)
|
||||
EOF
|
||||
|
||||
git config user.email "vscode@microsoft.com"
|
||||
git config user.name "VSCode"
|
||||
displayName: Prepare tooling
|
||||
git config user.email "vscode@microsoft.com"
|
||||
git config user.name "VSCode"
|
||||
displayName: Prepare tooling
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF
|
||||
echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)"
|
||||
git checkout FETCH_HEAD
|
||||
condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' '))
|
||||
displayName: Checkout override commit
|
||||
- script: |
|
||||
set -e
|
||||
git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF
|
||||
echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)"
|
||||
git checkout FETCH_HEAD
|
||||
condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' '))
|
||||
displayName: Checkout override commit
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
|
||||
displayName: Merge distro
|
||||
- script: |
|
||||
set -e
|
||||
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
|
||||
displayName: Merge distro
|
||||
|
|
|
@ -4,37 +4,37 @@ parameters:
|
|||
|
||||
steps:
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- task: AzureKeyVault@1
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
- task: AzureKeyVault@1
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
"machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
"machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII
|
||||
|
||||
exec { git config user.email "vscode@microsoft.com" }
|
||||
exec { git config user.name "VSCode" }
|
||||
displayName: Prepare tooling
|
||||
exec { git config user.email "vscode@microsoft.com" }
|
||||
exec { git config user.name "VSCode" }
|
||||
displayName: Prepare tooling
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
exec { git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $(VSCODE_DISTRO_REF) }
|
||||
Write-Host "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)"
|
||||
exec { git checkout FETCH_HEAD }
|
||||
condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' '))
|
||||
displayName: Checkout override commit
|
||||
exec { git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $(VSCODE_DISTRO_REF) }
|
||||
Write-Host "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)"
|
||||
exec { git checkout FETCH_HEAD }
|
||||
condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' '))
|
||||
displayName: Checkout override commit
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") }
|
||||
displayName: Merge distro
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") }
|
||||
displayName: Merge distro
|
||||
|
|
|
@ -7,7 +7,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
# allow-any-unicode-next-line
|
||||
|
|
|
@ -53,7 +53,7 @@ stages:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- powershell: |
|
||||
|
@ -167,7 +167,7 @@ stages:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- script: |
|
||||
|
|
|
@ -7,7 +7,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
|
|
|
@ -32,7 +32,7 @@ steps:
|
|||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
KeyVaultName: vscode-build-secrets
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
|
|
|
@ -513,18 +513,36 @@ function createL10nBundleForExtension(extensionFolderName) {
|
|||
// For source code of extensions
|
||||
`extensions/${extensionFolderName}/src/**/*.{ts,tsx}`,
|
||||
// For any dependencies pulled in (think vscode-css-languageservice or @vscode/emmet-helper)
|
||||
`extensions/${extensionFolderName}/node_modules/**/*.{js,jsx}`
|
||||
`extensions/${extensionFolderName}/node_modules/**/*.{js,jsx}`,
|
||||
// For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle
|
||||
`extensions/${extensionFolderName}/node_modules/**/bundle.l10n.json`,
|
||||
]).pipe((0, event_stream_1.writeArray)((err, files) => {
|
||||
if (err) {
|
||||
result.emit('error', err);
|
||||
return;
|
||||
}
|
||||
const json = (0, l10n_dev_1.getL10nJson)(files
|
||||
.filter(file => file.isBuffer())
|
||||
const buffers = files.filter(file => file.isBuffer());
|
||||
const json = (0, l10n_dev_1.getL10nJson)(buffers
|
||||
.filter(file => path.extname(file.path) !== '.json')
|
||||
.map(file => ({
|
||||
contents: file.contents.toString('utf8'),
|
||||
extension: path.extname(file.path)
|
||||
})));
|
||||
buffers
|
||||
.filter(file => path.extname(file.path) === '.json')
|
||||
.forEach(file => {
|
||||
const bundleJson = JSON.parse(file.contents.toString('utf8'));
|
||||
for (const key in bundleJson) {
|
||||
if (
|
||||
// some validation of the bundle.l10n.json format
|
||||
typeof bundleJson[key] !== 'string' &&
|
||||
(typeof bundleJson[key].message !== 'string' || !Array.isArray(bundleJson[key].comment))) {
|
||||
console.error(`Invalid bundle.l10n.json file. The value for key ${key} is not in the expected format. Skipping key...`);
|
||||
continue;
|
||||
}
|
||||
json[key] = bundleJson[key];
|
||||
}
|
||||
});
|
||||
if (Object.keys(json).length > 0) {
|
||||
result.emit('data', new File({
|
||||
path: `extensions/${extensionFolderName}/bundle.l10n.json`,
|
||||
|
|
|
@ -585,20 +585,41 @@ function createL10nBundleForExtension(extensionFolderName: string): ThroughStrea
|
|||
// For source code of extensions
|
||||
`extensions/${extensionFolderName}/src/**/*.{ts,tsx}`,
|
||||
// For any dependencies pulled in (think vscode-css-languageservice or @vscode/emmet-helper)
|
||||
`extensions/${extensionFolderName}/node_modules/**/*.{js,jsx}`
|
||||
`extensions/${extensionFolderName}/node_modules/**/*.{js,jsx}`,
|
||||
// For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle
|
||||
`extensions/${extensionFolderName}/node_modules/**/bundle.l10n.json`,
|
||||
]).pipe(writeArray((err, files: File[]) => {
|
||||
if (err) {
|
||||
result.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
const json = getL10nJson(files
|
||||
.filter(file => file.isBuffer())
|
||||
const buffers = files.filter(file => file.isBuffer());
|
||||
|
||||
const json = getL10nJson(buffers
|
||||
.filter(file => path.extname(file.path) !== '.json')
|
||||
.map(file => ({
|
||||
contents: file.contents.toString('utf8'),
|
||||
extension: path.extname(file.path)
|
||||
})));
|
||||
|
||||
buffers
|
||||
.filter(file => path.extname(file.path) === '.json')
|
||||
.forEach(file => {
|
||||
const bundleJson = JSON.parse(file.contents.toString('utf8'));
|
||||
for (const key in bundleJson) {
|
||||
if (
|
||||
// some validation of the bundle.l10n.json format
|
||||
typeof bundleJson[key] !== 'string' &&
|
||||
(typeof bundleJson[key].message !== 'string' || !Array.isArray(bundleJson[key].comment))
|
||||
) {
|
||||
console.error(`Invalid bundle.l10n.json file. The value for key ${key} is not in the expected format. Skipping key...`);
|
||||
continue;
|
||||
}
|
||||
json[key] = bundleJson[key];
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(json).length > 0) {
|
||||
result.emit('data', new File({
|
||||
path: `extensions/${extensionFolderName}/bundle.l10n.json`,
|
||||
|
|
|
@ -6,4 +6,4 @@ AddToPath=Agregar a PATH (disponible despu
|
|||
RunAfter=Ejecutar %1 después de la instalación
|
||||
Other=Otros:
|
||||
SourceFile=Archivo de origen %1
|
||||
OpenWithCodeContextMenu=Abrir con %1
|
||||
OpenWithCodeContextMenu=Abrir &con %1
|
||||
|
|
|
@ -8,13 +8,16 @@ use indicatif::ProgressBar;
|
|||
use crate::{
|
||||
self_update::SelfUpdate,
|
||||
update_service::UpdateService,
|
||||
util::{errors::AnyError, input::ProgressBarReporter},
|
||||
util::{errors::AnyError, http::ReqwestSimpleHttp, input::ProgressBarReporter},
|
||||
};
|
||||
|
||||
use super::{args::StandaloneUpdateArgs, CommandContext};
|
||||
|
||||
pub async fn update(ctx: CommandContext, args: StandaloneUpdateArgs) -> Result<i32, AnyError> {
|
||||
let update_service = UpdateService::new(ctx.log.clone(), ctx.http.clone());
|
||||
let update_service = UpdateService::new(
|
||||
ctx.log.clone(),
|
||||
ReqwestSimpleHttp::with_client(ctx.http.clone()),
|
||||
);
|
||||
let update_service = SelfUpdate::new(&update_service)?;
|
||||
|
||||
let current_version = update_service.get_current_release().await?;
|
||||
|
|
|
@ -10,7 +10,13 @@ use lazy_static::lazy_static;
|
|||
use crate::options::Quality;
|
||||
|
||||
pub const CONTROL_PORT: u16 = 31545;
|
||||
pub const PROTOCOL_VERSION: u32 = 1;
|
||||
|
||||
/// Protocol version sent to clients. This can be used to indiciate new or
|
||||
/// changed capabilities that clients may wish to leverage.
|
||||
/// 1 - Initial protocol version
|
||||
/// 2 - Addition of `serve.compressed` property to control whether servermsg's
|
||||
/// are compressed bidirectionally.
|
||||
pub const PROTOCOL_VERSION: u32 = 2;
|
||||
|
||||
pub const VSCODE_CLI_VERSION: Option<&'static str> = option_env!("VSCODE_CLI_VERSION");
|
||||
pub const VSCODE_CLI_AI_KEY: Option<&'static str> = option_env!("VSCODE_CLI_AI_KEY");
|
||||
|
|
|
@ -8,6 +8,7 @@ pub mod dev_tunnels;
|
|||
pub mod legal;
|
||||
pub mod paths;
|
||||
|
||||
mod socket_signal;
|
||||
mod control_server;
|
||||
mod name_generator;
|
||||
mod port_forwarder;
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::util::command::{capture_command, kill_tree};
|
|||
use crate::util::errors::{
|
||||
wrap, AnyError, ExtensionInstallFailed, MissingEntrypointError, WrappedError,
|
||||
};
|
||||
use crate::util::http;
|
||||
use crate::util::http::{self, SimpleHttp};
|
||||
use crate::util::io::SilentCopyProgress;
|
||||
use crate::util::machine::process_exists;
|
||||
use crate::{debug, info, log, span, spanf, trace, warning};
|
||||
|
@ -170,14 +170,22 @@ impl ResolvedServerParams {
|
|||
}
|
||||
|
||||
impl ServerParamsRaw {
|
||||
pub async fn resolve(self, log: &log::Logger) -> Result<ResolvedServerParams, AnyError> {
|
||||
pub async fn resolve(
|
||||
self,
|
||||
log: &log::Logger,
|
||||
http: impl SimpleHttp + Send + Sync + 'static,
|
||||
) -> Result<ResolvedServerParams, AnyError> {
|
||||
Ok(ResolvedServerParams {
|
||||
release: self.get_or_fetch_commit_id(log).await?,
|
||||
release: self.get_or_fetch_commit_id(log, http).await?,
|
||||
code_server_args: self.code_server_args,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_or_fetch_commit_id(&self, log: &log::Logger) -> Result<Release, AnyError> {
|
||||
async fn get_or_fetch_commit_id(
|
||||
&self,
|
||||
log: &log::Logger,
|
||||
http: impl SimpleHttp + Send + Sync + 'static,
|
||||
) -> Result<Release, AnyError> {
|
||||
let target = match self.headless {
|
||||
true => TargetKind::Server,
|
||||
false => TargetKind::Web,
|
||||
|
@ -193,7 +201,7 @@ impl ServerParamsRaw {
|
|||
});
|
||||
}
|
||||
|
||||
UpdateService::new(log.clone(), reqwest::Client::new())
|
||||
UpdateService::new(log.clone(), http)
|
||||
.get_latest_commit(self.platform, target, self.quality)
|
||||
.await
|
||||
}
|
||||
|
@ -285,6 +293,7 @@ async fn install_server_if_needed(
|
|||
log: &log::Logger,
|
||||
paths: &ServerPaths,
|
||||
release: &Release,
|
||||
http: impl SimpleHttp + Send + Sync + 'static,
|
||||
) -> Result<(), AnyError> {
|
||||
if paths.executable.exists() {
|
||||
info!(
|
||||
|
@ -298,7 +307,7 @@ async fn install_server_if_needed(
|
|||
let tar_file_path = spanf!(
|
||||
log,
|
||||
log.span("server.download"),
|
||||
download_server(&paths.server_dir, release, log)
|
||||
download_server(&paths.server_dir, release, log, http)
|
||||
)?;
|
||||
|
||||
span!(
|
||||
|
@ -314,28 +323,21 @@ async fn download_server(
|
|||
path: &Path,
|
||||
release: &Release,
|
||||
log: &log::Logger,
|
||||
http: impl SimpleHttp + Send + Sync + 'static,
|
||||
) -> Result<PathBuf, AnyError> {
|
||||
let response = UpdateService::new(log.clone(), reqwest::Client::new())
|
||||
let response = UpdateService::new(log.clone(), http)
|
||||
.get_download_stream(release)
|
||||
.await?;
|
||||
|
||||
let mut save_path = path.to_owned();
|
||||
|
||||
let fname = response
|
||||
.url()
|
||||
.path_segments()
|
||||
.and_then(|segments| segments.last())
|
||||
.and_then(|name| if name.is_empty() { None } else { Some(name) })
|
||||
.unwrap_or("tmp.zip");
|
||||
save_path.push("archive");
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Downloading VS Code server {} -> {}",
|
||||
response.url(),
|
||||
"Downloading VS Code server -> {}",
|
||||
save_path.display()
|
||||
);
|
||||
|
||||
save_path.push(fname);
|
||||
http::download_into_file(
|
||||
&save_path,
|
||||
log.get_download_logger("server download progress:"),
|
||||
|
@ -402,18 +404,20 @@ async fn do_extension_install_on_running_server(
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ServerBuilder<'a> {
|
||||
pub struct ServerBuilder<'a, Http: SimpleHttp + Send + Sync + Clone> {
|
||||
logger: &'a log::Logger,
|
||||
server_params: &'a ResolvedServerParams,
|
||||
last_used: LastUsedServers<'a>,
|
||||
server_paths: ServerPaths,
|
||||
http: Http,
|
||||
}
|
||||
|
||||
impl<'a> ServerBuilder<'a> {
|
||||
impl<'a, Http: SimpleHttp + Send + Sync + Clone + 'static> ServerBuilder<'a, Http> {
|
||||
pub fn new(
|
||||
logger: &'a log::Logger,
|
||||
server_params: &'a ResolvedServerParams,
|
||||
launcher_paths: &'a LauncherPaths,
|
||||
http: Http,
|
||||
) -> Self {
|
||||
Self {
|
||||
logger,
|
||||
|
@ -422,6 +426,7 @@ impl<'a> ServerBuilder<'a> {
|
|||
server_paths: server_params
|
||||
.as_installed_server()
|
||||
.server_paths(launcher_paths),
|
||||
http,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -476,8 +481,13 @@ impl<'a> ServerBuilder<'a> {
|
|||
pub async fn setup(&self) -> Result<(), AnyError> {
|
||||
debug!(self.logger, "Installing and setting up VS Code Server...");
|
||||
check_and_create_dir(&self.server_paths.server_dir).await?;
|
||||
install_server_if_needed(self.logger, &self.server_paths, &self.server_params.release)
|
||||
.await?;
|
||||
install_server_if_needed(
|
||||
self.logger,
|
||||
&self.server_paths,
|
||||
&self.server_params.release,
|
||||
self.http.clone(),
|
||||
)
|
||||
.await?;
|
||||
debug!(self.logger, "Server setup complete");
|
||||
|
||||
match self.last_used.add(self.server_params.as_installed_server()) {
|
||||
|
|
|
@ -7,19 +7,24 @@ use crate::constants::{CONTROL_PORT, PROTOCOL_VERSION, VSCODE_CLI_VERSION};
|
|||
use crate::log;
|
||||
use crate::self_update::SelfUpdate;
|
||||
use crate::state::LauncherPaths;
|
||||
use crate::tunnels::protocol::HttpRequestParams;
|
||||
use crate::tunnels::socket_signal::CloseReason;
|
||||
use crate::update_service::{Platform, UpdateService};
|
||||
use crate::util::errors::{
|
||||
wrap, AnyError, MismatchedLaunchModeError, NoAttachedServerError, ServerWriteError,
|
||||
};
|
||||
use crate::util::http::{
|
||||
DelegatedHttpRequest, DelegatedSimpleHttp, FallbackSimpleHttp, ReqwestSimpleHttp,
|
||||
};
|
||||
use crate::util::io::SilentCopyProgress;
|
||||
use crate::util::sync::{new_barrier, Barrier};
|
||||
use opentelemetry::trace::SpanKind;
|
||||
use opentelemetry::KeyValue;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader};
|
||||
|
@ -34,14 +39,16 @@ use super::paths::prune_stopped_servers;
|
|||
use super::port_forwarder::{PortForwarding, PortForwardingProcessor};
|
||||
use super::protocol::{
|
||||
CallServerHttpParams, CallServerHttpResult, ClientRequestMethod, EmptyResult, ErrorResponse,
|
||||
ForwardParams, ForwardResult, GetHostnameResponse, RefServerMessageParams, ResponseError,
|
||||
ServeParams, ServerLog, ServerMessageParams, ServerRequestMethod, SuccessResponse,
|
||||
ToClientRequest, ToServerRequest, UnforwardParams, UpdateParams, UpdateResult, VersionParams,
|
||||
ForwardParams, ForwardResult, GetHostnameResponse, ResponseError, ServeParams, ServerLog,
|
||||
ServerMessageParams, ServerRequestMethod, SuccessResponse, ToClientRequest, ToServerRequest,
|
||||
UnforwardParams, UpdateParams, UpdateResult, VersionParams,
|
||||
};
|
||||
use super::server_bridge::{get_socket_rw_stream, FromServerMessage, ServerBridge};
|
||||
use super::server_bridge::{get_socket_rw_stream, ServerBridge};
|
||||
use super::socket_signal::{ClientMessageDecoder, ServerMessageSink, SocketSignal};
|
||||
|
||||
type ServerBridgeList = Option<Vec<(u16, ServerBridge)>>;
|
||||
type ServerBridgeListLock = Arc<Mutex<ServerBridgeList>>;
|
||||
type HttpRequestsMap = Arc<std::sync::Mutex<HashMap<u32, DelegatedHttpRequest>>>;
|
||||
type CodeServerCell = Arc<Mutex<Option<SocketCodeServer>>>;
|
||||
|
||||
struct HandlerContext {
|
||||
|
@ -67,6 +74,17 @@ struct HandlerContext {
|
|||
port_forwarding: PortForwarding,
|
||||
/// install platform for the VS Code server
|
||||
platform: Platform,
|
||||
/// http client to make download/update requests
|
||||
http: FallbackSimpleHttp,
|
||||
/// requests being served by the client
|
||||
http_requests: HttpRequestsMap,
|
||||
}
|
||||
|
||||
static MESSAGE_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
// Gets a next incrementing number that can be used in logs
|
||||
pub fn next_message_id() -> u32 {
|
||||
MESSAGE_ID_COUNTER.fetch_add(1, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
impl HandlerContext {
|
||||
|
@ -105,39 +123,6 @@ enum ServerSignal {
|
|||
Respawn,
|
||||
}
|
||||
|
||||
struct CloseReason(String);
|
||||
|
||||
enum SocketSignal {
|
||||
/// Signals bytes to send to the socket.
|
||||
Send(Vec<u8>),
|
||||
/// Closes the socket (e.g. as a result of an error)
|
||||
CloseWith(CloseReason),
|
||||
/// Disposes ServerBridge corresponding to an ID
|
||||
CloseServerBridge(u16),
|
||||
}
|
||||
|
||||
impl SocketSignal {
|
||||
fn from_message<T>(msg: &T) -> Self
|
||||
where
|
||||
T: Serialize + ?Sized,
|
||||
{
|
||||
SocketSignal::Send(rmp_serde::to_vec_named(msg).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromServerMessage for SocketSignal {
|
||||
fn from_server_message(i: u16, body: &[u8]) -> Self {
|
||||
SocketSignal::from_message(&ToClientRequest {
|
||||
id: None,
|
||||
params: ClientRequestMethod::servermsg(RefServerMessageParams { i, body }),
|
||||
})
|
||||
}
|
||||
|
||||
fn from_closed_server_bridge(i: u16) -> Self {
|
||||
SocketSignal::CloseServerBridge(i)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerTermination {
|
||||
/// Whether the server should be respawned in a new binary (see ServerSignal.Respawn).
|
||||
pub respawn: bool,
|
||||
|
@ -279,7 +264,7 @@ async fn process_socket(
|
|||
platform: Platform,
|
||||
) -> SocketStats {
|
||||
let (socket_tx, mut socket_rx) = mpsc::channel(4);
|
||||
|
||||
let http_requests = Arc::new(std::sync::Mutex::new(HashMap::new()));
|
||||
let rx_counter = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let server_bridges: ServerBridgeListLock = Arc::new(Mutex::new(Some(vec![])));
|
||||
|
@ -287,6 +272,8 @@ async fn process_socket(
|
|||
let barrier_ctx = exit_barrier.clone();
|
||||
let log_ctx = log.clone();
|
||||
let rx_counter_ctx = rx_counter.clone();
|
||||
let http_requests_ctx = http_requests.clone();
|
||||
let (http_delegated, mut http_rx) = DelegatedSimpleHttp::new(log_ctx.clone());
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut ctx = HandlerContext {
|
||||
|
@ -301,6 +288,8 @@ async fn process_socket(
|
|||
server_bridges: server_bridges_lock,
|
||||
port_forwarding,
|
||||
platform,
|
||||
http: FallbackSimpleHttp::new(ReqwestSimpleHttp::new(), http_delegated),
|
||||
http_requests: http_requests_ctx,
|
||||
};
|
||||
|
||||
send_version(&ctx.socket_tx).await;
|
||||
|
@ -324,6 +313,25 @@ async fn process_socket(
|
|||
writehalf.shutdown().await.ok();
|
||||
break;
|
||||
},
|
||||
Some(r) = http_rx.recv() => {
|
||||
let id = next_message_id();
|
||||
let serialized = rmp_serde::to_vec_named(&ToClientRequest {
|
||||
id: None,
|
||||
params: ClientRequestMethod::makehttpreq(HttpRequestParams {
|
||||
url: &r.url,
|
||||
method: r.method,
|
||||
req_id: id,
|
||||
}),
|
||||
})
|
||||
.unwrap();
|
||||
http_requests.lock().unwrap().insert(id, r);
|
||||
|
||||
tx_counter += serialized.len();
|
||||
if let Err(e) = writehalf.write_all(&serialized).await {
|
||||
debug!(log, "Closing connection: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
recv = socket_rx.recv() => match recv {
|
||||
None => break,
|
||||
Some(message) => match message {
|
||||
|
@ -509,6 +517,7 @@ async fn dispatch_next(req: ToServerRequest, ctx: &mut HandlerContext, did_updat
|
|||
}
|
||||
ServerRequestMethod::serve(params) => {
|
||||
let log = ctx.log.clone();
|
||||
let http = ctx.http.clone();
|
||||
let server_bridges = ctx.server_bridges.clone();
|
||||
let code_server_args = ctx.code_server_args.clone();
|
||||
let code_server = ctx.code_server.clone();
|
||||
|
@ -519,6 +528,7 @@ async fn dispatch_next(req: ToServerRequest, ctx: &mut HandlerContext, did_updat
|
|||
"serve",
|
||||
handle_serve(
|
||||
log,
|
||||
http,
|
||||
server_bridges,
|
||||
code_server_args,
|
||||
platform,
|
||||
|
@ -538,7 +548,7 @@ async fn dispatch_next(req: ToServerRequest, ctx: &mut HandlerContext, did_updat
|
|||
}
|
||||
ServerRequestMethod::update(p) => {
|
||||
dispatch_blocking!("update", async {
|
||||
let r = handle_update(&ctx.log, &p).await;
|
||||
let r = handle_update(&ctx.http, &ctx.log, &p).await;
|
||||
if matches!(&r, Ok(u) if u.did_update) {
|
||||
*did_update = true;
|
||||
}
|
||||
|
@ -567,6 +577,26 @@ async fn dispatch_next(req: ToServerRequest, ctx: &mut HandlerContext, did_updat
|
|||
let port_forwarding = ctx.port_forwarding.clone();
|
||||
dispatch_async!("unforward", handle_unforward(log, port_forwarding, p));
|
||||
}
|
||||
ServerRequestMethod::httpheaders(p) => {
|
||||
if let Some(req) = ctx.http_requests.lock().unwrap().get(&p.req_id) {
|
||||
req.initial_response(p.status_code, p.headers);
|
||||
}
|
||||
success!(ctx.socket_tx, EmptyResult {});
|
||||
}
|
||||
ServerRequestMethod::httpbody(p) => {
|
||||
{
|
||||
let mut reqs = ctx.http_requests.lock().unwrap();
|
||||
if let Some(req) = reqs.get(&p.req_id) {
|
||||
if !p.segment.is_empty() {
|
||||
req.body(p.segment);
|
||||
}
|
||||
if p.complete {
|
||||
reqs.remove(&p.req_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
success!(ctx.socket_tx, EmptyResult {});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -594,6 +624,7 @@ impl log::LogSink for ServerOutputSink {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_serve(
|
||||
log: log::Logger,
|
||||
http: FallbackSimpleHttp,
|
||||
server_bridges: ServerBridgeListLock,
|
||||
mut code_server_args: CodeServerArgs,
|
||||
platform: Platform,
|
||||
|
@ -607,15 +638,19 @@ async fn handle_serve(
|
|||
.install_extensions
|
||||
.extend(params.extensions.into_iter());
|
||||
|
||||
let resolved = ServerParamsRaw {
|
||||
let params_raw = ServerParamsRaw {
|
||||
commit_id: params.commit_id,
|
||||
quality: params.quality,
|
||||
code_server_args,
|
||||
headless: true,
|
||||
platform,
|
||||
}
|
||||
.resolve(&log)
|
||||
.await?;
|
||||
};
|
||||
|
||||
let resolved = if params.use_local_download {
|
||||
params_raw.resolve(&log, http.delegated()).await
|
||||
} else {
|
||||
params_raw.resolve(&log, http.clone()).await
|
||||
}?;
|
||||
|
||||
let mut server_ref = code_server.lock().await;
|
||||
let server = match &*server_ref {
|
||||
|
@ -624,15 +659,27 @@ async fn handle_serve(
|
|||
let install_log = log.tee(ServerOutputSink {
|
||||
tx: socket_tx.clone(),
|
||||
});
|
||||
let sb = ServerBuilder::new(&install_log, &resolved, &launcher_paths);
|
||||
|
||||
let server = match sb.get_running().await? {
|
||||
Some(AnyCodeServer::Socket(s)) => s,
|
||||
Some(_) => return Err(AnyError::from(MismatchedLaunchModeError())),
|
||||
None => {
|
||||
sb.setup().await?;
|
||||
sb.listen_on_default_socket().await?
|
||||
}
|
||||
macro_rules! do_setup {
|
||||
($sb:expr) => {
|
||||
match $sb.get_running().await? {
|
||||
Some(AnyCodeServer::Socket(s)) => s,
|
||||
Some(_) => return Err(AnyError::from(MismatchedLaunchModeError())),
|
||||
None => {
|
||||
$sb.setup().await?;
|
||||
$sb.listen_on_default_socket().await?
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let server = if params.use_local_download {
|
||||
let sb =
|
||||
ServerBuilder::new(&install_log, &resolved, &launcher_paths, http.delegated());
|
||||
do_setup!(sb)
|
||||
} else {
|
||||
let sb = ServerBuilder::new(&install_log, &resolved, &launcher_paths, http);
|
||||
do_setup!(sb)
|
||||
};
|
||||
|
||||
server_ref.replace(server.clone());
|
||||
|
@ -640,7 +687,15 @@ async fn handle_serve(
|
|||
}
|
||||
};
|
||||
|
||||
attach_server_bridge(&log, server, socket_tx, server_bridges, params.socket_id).await?;
|
||||
attach_server_bridge(
|
||||
&log,
|
||||
server,
|
||||
socket_tx,
|
||||
server_bridges,
|
||||
params.socket_id,
|
||||
params.compress,
|
||||
)
|
||||
.await?;
|
||||
Ok(EmptyResult {})
|
||||
}
|
||||
|
||||
|
@ -650,8 +705,22 @@ async fn attach_server_bridge(
|
|||
socket_tx: mpsc::Sender<SocketSignal>,
|
||||
server_bridges: ServerBridgeListLock,
|
||||
socket_id: u16,
|
||||
compress: bool,
|
||||
) -> Result<u16, AnyError> {
|
||||
let attached_fut = ServerBridge::new(&code_server.socket, socket_id, &socket_tx).await;
|
||||
let (server_messages, decoder) = if compress {
|
||||
(
|
||||
ServerMessageSink::new_compressed(socket_tx),
|
||||
ClientMessageDecoder::new_compressed(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
ServerMessageSink::new_plain(socket_tx),
|
||||
ClientMessageDecoder::new_plain(),
|
||||
)
|
||||
};
|
||||
|
||||
let attached_fut =
|
||||
ServerBridge::new(&code_server.socket, socket_id, server_messages, decoder).await;
|
||||
|
||||
match attached_fut {
|
||||
Ok(a) => {
|
||||
|
@ -699,8 +768,12 @@ async fn handle_prune(paths: &LauncherPaths) -> Result<Vec<String>, AnyError> {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_update(log: &log::Logger, params: &UpdateParams) -> Result<UpdateResult, AnyError> {
|
||||
let update_service = UpdateService::new(log.clone(), reqwest::Client::new());
|
||||
async fn handle_update(
|
||||
http: &FallbackSimpleHttp,
|
||||
log: &log::Logger,
|
||||
params: &UpdateParams,
|
||||
) -> Result<UpdateResult, AnyError> {
|
||||
let update_service = UpdateService::new(log.clone(), http.clone());
|
||||
let updater = SelfUpdate::new(&update_service)?;
|
||||
let latest_release = updater.get_current_release().await?;
|
||||
let up_to_date = updater.is_up_to_date_with(&latest_release);
|
||||
|
|
|
@ -11,15 +11,29 @@ use serde::{Deserialize, Serialize};
|
|||
#[serde(tag = "method", content = "params")]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum ServerRequestMethod {
|
||||
/// Request from the client to start the VS Code server. It will download the
|
||||
/// requested version, if necessary.
|
||||
serve(ServeParams),
|
||||
/// Prunes unused servers on the CLI.
|
||||
prune,
|
||||
/// Empty ping/pong method used for liveness check.
|
||||
ping(EmptyResult),
|
||||
/// Forwards a port from the machine the CLI is running on.
|
||||
forward(ForwardParams),
|
||||
/// Stops forwarding a port from the machine the CLI is running on.
|
||||
unforward(UnforwardParams),
|
||||
/// Gets the hostname of the machine the CLI is running on.
|
||||
gethostname(EmptyResult),
|
||||
/// Checks for or applies an update to the CLI.
|
||||
update(UpdateParams),
|
||||
/// Sent when the remote instance of VS Code has a message for the server.
|
||||
servermsg(ServerMessageParams),
|
||||
/// Sent to make an http call on the local VS Code server.
|
||||
callserverhttp(CallServerHttpParams),
|
||||
/// Sent once with data in response to an `makehttpreq` from the server.
|
||||
httpheaders(HttpHeadersParams),
|
||||
/// Sent (repeatedly) with data in response to an `makehttpreq` from the server.
|
||||
httpbody(HttpBodyParams),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
|
@ -28,9 +42,32 @@ pub enum ServerRequestMethod {
|
|||
pub enum ClientRequestMethod<'a> {
|
||||
servermsg(RefServerMessageParams<'a>),
|
||||
serverlog(ServerLog<'a>),
|
||||
makehttpreq(HttpRequestParams<'a>),
|
||||
version(VersionParams),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct HttpBodyParams {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub segment: Vec<u8>,
|
||||
pub complete: bool,
|
||||
pub req_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct HttpRequestParams<'a> {
|
||||
pub url: &'a str,
|
||||
pub method: &'static str,
|
||||
pub req_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct HttpHeadersParams {
|
||||
pub status_code: u16,
|
||||
pub headers: Vec<(String, String)>,
|
||||
pub req_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ForwardParams {
|
||||
pub port: u16,
|
||||
|
@ -52,6 +89,11 @@ pub struct ServeParams {
|
|||
pub commit_id: Option<String>,
|
||||
pub quality: Quality,
|
||||
pub extensions: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub use_local_download: bool,
|
||||
/// If true, the client and server should gzip servermsg's sent in either direction.
|
||||
#[serde(default)]
|
||||
pub compress: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
|
@ -84,14 +126,14 @@ pub struct UpdateResult {
|
|||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ToServerRequest {
|
||||
pub id: Option<u8>,
|
||||
pub id: Option<u32>,
|
||||
#[serde(flatten)]
|
||||
pub params: ServerRequestMethod,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct ToClientRequest<'a> {
|
||||
pub id: Option<u8>,
|
||||
pub id: Option<u32>,
|
||||
#[serde(flatten)]
|
||||
pub params: ClientRequestMethod<'a>,
|
||||
}
|
||||
|
@ -101,13 +143,13 @@ pub struct SuccessResponse<T>
|
|||
where
|
||||
T: Serialize,
|
||||
{
|
||||
pub id: u8,
|
||||
pub id: u32,
|
||||
pub result: T,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub id: u8,
|
||||
pub id: u32,
|
||||
pub error: ResponseError,
|
||||
}
|
||||
|
||||
|
|
|
@ -7,18 +7,15 @@ use std::path::Path;
|
|||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{unix::OwnedWriteHalf, UnixStream},
|
||||
sync::mpsc::Sender,
|
||||
};
|
||||
|
||||
use crate::util::errors::{wrap, AnyError};
|
||||
|
||||
use super::socket_signal::{ClientMessageDecoder, ServerMessageSink};
|
||||
|
||||
pub struct ServerBridge {
|
||||
write: OwnedWriteHalf,
|
||||
}
|
||||
|
||||
pub trait FromServerMessage {
|
||||
fn from_server_message(index: u16, message: &[u8]) -> Self;
|
||||
fn from_closed_server_bridge(i: u16) -> Self;
|
||||
decoder: ClientMessageDecoder,
|
||||
}
|
||||
|
||||
pub async fn get_socket_rw_stream(path: &Path) -> Result<UnixStream, AnyError> {
|
||||
|
@ -38,25 +35,26 @@ pub async fn get_socket_rw_stream(path: &Path) -> Result<UnixStream, AnyError> {
|
|||
const BUFFER_SIZE: usize = 65536;
|
||||
|
||||
impl ServerBridge {
|
||||
pub async fn new<T>(path: &Path, index: u16, target: &Sender<T>) -> Result<Self, AnyError>
|
||||
where
|
||||
T: 'static + FromServerMessage + Send,
|
||||
{
|
||||
pub async fn new(
|
||||
path: &Path,
|
||||
index: u16,
|
||||
mut target: ServerMessageSink,
|
||||
decoder: ClientMessageDecoder,
|
||||
) -> Result<Self, AnyError> {
|
||||
let stream = get_socket_rw_stream(path).await?;
|
||||
let (mut read, write) = stream.into_split();
|
||||
|
||||
let tx = target.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut read_buf = vec![0; BUFFER_SIZE];
|
||||
loop {
|
||||
match read.read(&mut read_buf).await {
|
||||
Err(_) => return,
|
||||
Ok(0) => {
|
||||
let _ = tx.send(T::from_closed_server_bridge(index)).await;
|
||||
let _ = target.closed_server_bridge(index).await;
|
||||
return; // EOF
|
||||
}
|
||||
Ok(s) => {
|
||||
let send = tx.send(T::from_server_message(index, &read_buf[..s])).await;
|
||||
let send = target.server_message(index, &read_buf[..s]).await;
|
||||
if send.is_err() {
|
||||
return;
|
||||
}
|
||||
|
@ -65,11 +63,14 @@ impl ServerBridge {
|
|||
}
|
||||
});
|
||||
|
||||
Ok(ServerBridge { write })
|
||||
Ok(ServerBridge { write, decoder })
|
||||
}
|
||||
|
||||
pub async fn write(&mut self, b: Vec<u8>) -> std::io::Result<()> {
|
||||
self.write.write_all(&b).await?;
|
||||
let dec = self.decoder.decode(&b)?;
|
||||
if !dec.is_empty() {
|
||||
self.write.write_all(dec).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -14,13 +14,11 @@ use tokio::{
|
|||
|
||||
use crate::util::errors::{wrap, AnyError};
|
||||
|
||||
use super::socket_signal::{ClientMessageDecoder, ServerMessageSink};
|
||||
|
||||
pub struct ServerBridge {
|
||||
write_tx: mpsc::Sender<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub trait FromServerMessage {
|
||||
fn from_server_message(index: u16, message: &[u8]) -> Self;
|
||||
fn from_closed_server_bridge(i: u16) -> Self;
|
||||
decoder: ClientMessageDecoder,
|
||||
}
|
||||
|
||||
const BUFFER_SIZE: usize = 65536;
|
||||
|
@ -49,13 +47,14 @@ pub async fn get_socket_rw_stream(path: &Path) -> Result<NamedPipeClient, AnyErr
|
|||
}
|
||||
|
||||
impl ServerBridge {
|
||||
pub async fn new<T>(path: &Path, index: u16, target: &mpsc::Sender<T>) -> Result<Self, AnyError>
|
||||
where
|
||||
T: 'static + FromServerMessage + Send,
|
||||
{
|
||||
pub async fn new(
|
||||
path: &Path,
|
||||
index: u16,
|
||||
mut target: ServerMessageSink,
|
||||
decoder: ClientMessageDecoder,
|
||||
) -> Result<Self, AnyError> {
|
||||
let client = get_socket_rw_stream(path).await?;
|
||||
let (write_tx, mut write_rx) = mpsc::channel(4);
|
||||
let read_tx = target.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut read_buf = vec![0; BUFFER_SIZE];
|
||||
let mut pending_recv: Option<Vec<u8>> = None;
|
||||
|
@ -89,9 +88,7 @@ impl ServerBridge {
|
|||
match client.try_read(&mut read_buf) {
|
||||
Ok(0) => return, // EOF
|
||||
Ok(s) => {
|
||||
let send = read_tx
|
||||
.send(T::from_server_message(index, &read_buf[..s]))
|
||||
.await;
|
||||
let send = target.server_message(index, &read_buf[..s]).await;
|
||||
if send.is_err() {
|
||||
return;
|
||||
}
|
||||
|
@ -118,11 +115,14 @@ impl ServerBridge {
|
|||
}
|
||||
});
|
||||
|
||||
Ok(ServerBridge { write_tx })
|
||||
Ok(ServerBridge { write_tx, decoder })
|
||||
}
|
||||
|
||||
pub async fn write(&self, b: Vec<u8>) -> std::io::Result<()> {
|
||||
self.write_tx.send(b).await.ok();
|
||||
pub async fn write(&mut self, b: Vec<u8>) -> std::io::Result<()> {
|
||||
let dec = self.decoder.decode(&b)?;
|
||||
if !dec.is_empty() {
|
||||
self.write_tx.send(dec.to_vec()).await.ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
244
cli/src/tunnels/socket_signal.rs
Normal file
244
cli/src/tunnels/socket_signal.rs
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use serde::Serialize;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use super::protocol::{ClientRequestMethod, RefServerMessageParams, ToClientRequest};
|
||||
|
||||
pub struct CloseReason(pub String);
|
||||
|
||||
pub enum SocketSignal {
|
||||
/// Signals bytes to send to the socket.
|
||||
Send(Vec<u8>),
|
||||
/// Closes the socket (e.g. as a result of an error)
|
||||
CloseWith(CloseReason),
|
||||
/// Disposes ServerBridge corresponding to an ID
|
||||
CloseServerBridge(u16),
|
||||
}
|
||||
|
||||
impl SocketSignal {
|
||||
pub fn from_message<T>(msg: &T) -> Self
|
||||
where
|
||||
T: Serialize + ?Sized,
|
||||
{
|
||||
SocketSignal::Send(rmp_serde::to_vec_named(msg).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct that handling sending or closing a connected server socket.
|
||||
pub struct ServerMessageSink {
|
||||
tx: mpsc::Sender<SocketSignal>,
|
||||
flate: Option<FlateStream<CompressFlateAlgorithm>>,
|
||||
}
|
||||
|
||||
impl ServerMessageSink {
|
||||
pub fn new_plain(tx: mpsc::Sender<SocketSignal>) -> Self {
|
||||
Self { tx, flate: None }
|
||||
}
|
||||
|
||||
pub fn new_compressed(tx: mpsc::Sender<SocketSignal>) -> Self {
|
||||
Self {
|
||||
tx,
|
||||
flate: Some(FlateStream::new(CompressFlateAlgorithm(
|
||||
flate2::Compress::new(flate2::Compression::new(2), false),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn server_message(
|
||||
&mut self,
|
||||
i: u16,
|
||||
body: &[u8],
|
||||
) -> Result<(), mpsc::error::SendError<SocketSignal>> {
|
||||
let msg = {
|
||||
let body = self.get_server_msg_content(body);
|
||||
SocketSignal::from_message(&ToClientRequest {
|
||||
id: None,
|
||||
params: ClientRequestMethod::servermsg(RefServerMessageParams { i, body }),
|
||||
})
|
||||
};
|
||||
|
||||
self.tx.send(msg).await
|
||||
}
|
||||
|
||||
pub(crate) fn get_server_msg_content<'a: 'b, 'b>(&'a mut self, body: &'b [u8]) -> &'b [u8] {
|
||||
if let Some(flate) = &mut self.flate {
|
||||
if let Ok(compressed) = flate.process(body) {
|
||||
return compressed;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn closed_server_bridge(
|
||||
&mut self,
|
||||
i: u16,
|
||||
) -> Result<(), mpsc::error::SendError<SocketSignal>> {
|
||||
self.tx.send(SocketSignal::CloseServerBridge(i)).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientMessageDecoder {
|
||||
dec: Option<FlateStream<DecompressFlateAlgorithm>>,
|
||||
}
|
||||
|
||||
impl ClientMessageDecoder {
|
||||
pub fn new_plain() -> Self {
|
||||
ClientMessageDecoder { dec: None }
|
||||
}
|
||||
|
||||
pub fn new_compressed() -> Self {
|
||||
ClientMessageDecoder {
|
||||
dec: Some(FlateStream::new(DecompressFlateAlgorithm(
|
||||
flate2::Decompress::new(false),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode<'a: 'b, 'b>(&'a mut self, message: &'b [u8]) -> std::io::Result<&'b [u8]> {
|
||||
match &mut self.dec {
|
||||
Some(d) => d.process(message),
|
||||
None => Ok(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait FlateAlgorithm {
|
||||
fn total_in(&self) -> u64;
|
||||
fn total_out(&self) -> u64;
|
||||
fn process(
|
||||
&mut self,
|
||||
contents: &[u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<flate2::Status, std::io::Error>;
|
||||
}
|
||||
|
||||
struct DecompressFlateAlgorithm(flate2::Decompress);
|
||||
|
||||
impl FlateAlgorithm for DecompressFlateAlgorithm {
|
||||
fn total_in(&self) -> u64 {
|
||||
self.0.total_in()
|
||||
}
|
||||
|
||||
fn total_out(&self) -> u64 {
|
||||
self.0.total_out()
|
||||
}
|
||||
|
||||
fn process(
|
||||
&mut self,
|
||||
contents: &[u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<flate2::Status, std::io::Error> {
|
||||
self.0
|
||||
.decompress(contents, output, flate2::FlushDecompress::None)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
|
||||
}
|
||||
}
|
||||
|
||||
struct CompressFlateAlgorithm(flate2::Compress);
|
||||
|
||||
impl FlateAlgorithm for CompressFlateAlgorithm {
|
||||
fn total_in(&self) -> u64 {
|
||||
self.0.total_in()
|
||||
}
|
||||
|
||||
fn total_out(&self) -> u64 {
|
||||
self.0.total_out()
|
||||
}
|
||||
|
||||
fn process(
|
||||
&mut self,
|
||||
contents: &[u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<flate2::Status, std::io::Error> {
|
||||
self.0
|
||||
.compress(contents, output, flate2::FlushCompress::Sync)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
|
||||
}
|
||||
}
|
||||
|
||||
struct FlateStream<A>
|
||||
where
|
||||
A: FlateAlgorithm,
|
||||
{
|
||||
flate: A,
|
||||
output: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<A> FlateStream<A>
|
||||
where
|
||||
A: FlateAlgorithm,
|
||||
{
|
||||
pub fn new(alg: A) -> Self {
|
||||
Self {
|
||||
flate: alg,
|
||||
output: vec![0; 4096],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&mut self, contents: &[u8]) -> std::io::Result<&[u8]> {
|
||||
let mut out_offset = 0;
|
||||
let mut in_offset = 0;
|
||||
loop {
|
||||
let in_before = self.flate.total_in();
|
||||
let out_before = self.flate.total_out();
|
||||
|
||||
match self
|
||||
.flate
|
||||
.process(&contents[in_offset..], &mut self.output[out_offset..])
|
||||
{
|
||||
Ok(flate2::Status::Ok | flate2::Status::BufError) => {
|
||||
let processed_len = in_offset + (self.flate.total_in() - in_before) as usize;
|
||||
let output_len = out_offset + (self.flate.total_out() - out_before) as usize;
|
||||
if processed_len < contents.len() {
|
||||
// If we filled the output buffer but there's more data to compress,
|
||||
// extend the output buffer and keep compressing.
|
||||
out_offset = output_len;
|
||||
in_offset = processed_len;
|
||||
if output_len == self.output.len() {
|
||||
self.output.resize(self.output.len() * 2, 0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(&self.output[..output_len]);
|
||||
}
|
||||
Ok(flate2::Status::StreamEnd) => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
"unexpected stream end",
|
||||
))
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Note this useful idiom: importing names from outer (for mod tests) scope.
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_round_trips_compression() {
|
||||
let (tx, _) = mpsc::channel(1);
|
||||
let mut sink = ServerMessageSink::new_compressed(tx);
|
||||
let mut decompress = ClientMessageDecoder::new_compressed();
|
||||
|
||||
// 3000 and 30000 test resizing the buffer
|
||||
for msg_len in [3, 30, 300, 3000, 30000] {
|
||||
let vals = (0..msg_len).map(|v| v as u8).collect::<Vec<u8>>();
|
||||
let compressed = sink.get_server_msg_content(&vals);
|
||||
assert_ne!(compressed, vals);
|
||||
let decompressed = decompress.decode(compressed).unwrap();
|
||||
assert_eq!(decompressed.len(), vals.len());
|
||||
assert_eq!(decompressed, vals);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,16 +11,15 @@ use crate::{
|
|||
constants::VSCODE_CLI_UPDATE_ENDPOINT,
|
||||
debug, log, options, spanf,
|
||||
util::{
|
||||
errors::{
|
||||
AnyError, StatusError, UnsupportedPlatformError, UpdatesNotConfigured, WrappedError,
|
||||
},
|
||||
errors::{AnyError, UnsupportedPlatformError, UpdatesNotConfigured, WrappedError},
|
||||
http::{SimpleHttp, SimpleResponse},
|
||||
io::ReportCopyProgress,
|
||||
},
|
||||
};
|
||||
|
||||
/// Implementation of the VS Code Update service for use in the CLI.
|
||||
pub struct UpdateService {
|
||||
client: reqwest::Client,
|
||||
client: Box<dyn SimpleHttp + Send + Sync + 'static>,
|
||||
log: log::Logger,
|
||||
}
|
||||
|
||||
|
@ -54,8 +53,11 @@ fn quality_download_segment(quality: options::Quality) -> &'static str {
|
|||
}
|
||||
|
||||
impl UpdateService {
|
||||
pub fn new(log: log::Logger, client: reqwest::Client) -> Self {
|
||||
UpdateService { client, log }
|
||||
pub fn new(log: log::Logger, http: impl SimpleHttp + Send + Sync + 'static) -> Self {
|
||||
UpdateService {
|
||||
client: Box::new(http),
|
||||
log,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_release_by_semver_version(
|
||||
|
@ -78,14 +80,14 @@ impl UpdateService {
|
|||
quality_download_segment(quality),
|
||||
);
|
||||
|
||||
let response = spanf!(
|
||||
let mut response = spanf!(
|
||||
self.log,
|
||||
self.log.span("server.version.resolve"),
|
||||
self.client.get(download_url).send()
|
||||
self.client.make_request("GET", download_url)
|
||||
)?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(StatusError::from_res(response).await?.into());
|
||||
if !response.status_code.is_success() {
|
||||
return Err(response.into_err().await.into());
|
||||
}
|
||||
|
||||
let res = response.json::<UpdateServerVersion>().await?;
|
||||
|
@ -119,14 +121,14 @@ impl UpdateService {
|
|||
quality_download_segment(quality),
|
||||
);
|
||||
|
||||
let response = spanf!(
|
||||
let mut response = spanf!(
|
||||
self.log,
|
||||
self.log.span("server.version.resolve"),
|
||||
self.client.get(download_url).send()
|
||||
self.client.make_request("GET", download_url)
|
||||
)?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(StatusError::from_res(response).await?.into());
|
||||
if !response.status_code.is_success() {
|
||||
return Err(response.into_err().await.into());
|
||||
}
|
||||
|
||||
let res = response.json::<UpdateServerVersion>().await?;
|
||||
|
@ -142,10 +144,7 @@ impl UpdateService {
|
|||
}
|
||||
|
||||
/// Gets the download stream for the release.
|
||||
pub async fn get_download_stream(
|
||||
&self,
|
||||
release: &Release,
|
||||
) -> Result<reqwest::Response, AnyError> {
|
||||
pub async fn get_download_stream(&self, release: &Release) -> Result<SimpleResponse, AnyError> {
|
||||
let update_endpoint =
|
||||
VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?;
|
||||
let download_segment = release
|
||||
|
@ -161,9 +160,9 @@ impl UpdateService {
|
|||
quality_download_segment(release.quality),
|
||||
);
|
||||
|
||||
let response = reqwest::get(&download_url).await?;
|
||||
if !response.status().is_success() {
|
||||
return Err(StatusError::from_res(response).await?.into());
|
||||
let response = self.client.make_request("GET", download_url).await?;
|
||||
if !response.status_code.is_success() {
|
||||
return Err(response.into_err().await.into());
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
|
@ -220,7 +219,7 @@ pub enum Platform {
|
|||
DarwinARM64,
|
||||
WindowsX64,
|
||||
WindowsX86,
|
||||
WindowsARM64
|
||||
WindowsARM64,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
|
|
|
@ -57,9 +57,9 @@ where
|
|||
// Error generated by an unsuccessful HTTP response
|
||||
#[derive(Debug)]
|
||||
pub struct StatusError {
|
||||
url: String,
|
||||
status_code: u16,
|
||||
body: String,
|
||||
pub url: String,
|
||||
pub status_code: u16,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for StatusError {
|
||||
|
|
|
@ -2,17 +2,37 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
use crate::util::errors::{self, WrappedError};
|
||||
use crate::{
|
||||
constants::get_default_user_agent,
|
||||
log,
|
||||
util::errors::{self, WrappedError},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use core::panic;
|
||||
use futures::stream::TryStreamExt;
|
||||
use tokio::fs;
|
||||
use hyper::{
|
||||
header::{HeaderName, CONTENT_LENGTH},
|
||||
http::HeaderValue,
|
||||
HeaderMap, StatusCode,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{io, pin::Pin, str::FromStr, task::Poll};
|
||||
use tokio::{
|
||||
fs,
|
||||
io::{AsyncRead, AsyncReadExt},
|
||||
sync::mpsc,
|
||||
};
|
||||
use tokio_util::compat::FuturesAsyncReadCompatExt;
|
||||
|
||||
use super::io::{copy_async_progress, ReportCopyProgress};
|
||||
use super::{
|
||||
errors::{wrap, AnyError, StatusError},
|
||||
io::{copy_async_progress, ReadBuffer, ReportCopyProgress},
|
||||
};
|
||||
|
||||
pub async fn download_into_file<T>(
|
||||
filename: &std::path::Path,
|
||||
progress: T,
|
||||
res: reqwest::Response,
|
||||
mut res: SimpleResponse,
|
||||
) -> Result<fs::File, WrappedError>
|
||||
where
|
||||
T: ReportCopyProgress,
|
||||
|
@ -21,16 +41,323 @@ where
|
|||
.await
|
||||
.map_err(|e| errors::wrap(e, "failed to create file"))?;
|
||||
|
||||
let content_length = res.content_length().unwrap_or(0);
|
||||
let mut read = res
|
||||
.bytes_stream()
|
||||
.map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e))
|
||||
.into_async_read()
|
||||
.compat();
|
||||
let content_length = res
|
||||
.headers
|
||||
.get(CONTENT_LENGTH)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
copy_async_progress(progress, &mut read, &mut file, content_length)
|
||||
copy_async_progress(progress, &mut res.read, &mut file, content_length)
|
||||
.await
|
||||
.map_err(|e| errors::wrap(e, "failed to download file"))?;
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
pub struct SimpleResponse {
|
||||
pub status_code: StatusCode,
|
||||
pub headers: HeaderMap,
|
||||
pub read: Pin<Box<dyn Send + AsyncRead + 'static>>,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl SimpleResponse {
|
||||
pub fn generic_error(url: String) -> Self {
|
||||
let (_, rx) = mpsc::unbounded_channel();
|
||||
SimpleResponse {
|
||||
url,
|
||||
status_code: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
headers: HeaderMap::new(),
|
||||
read: Box::pin(DelegatedReader::new(rx)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the response into a StatusError
|
||||
pub async fn into_err(mut self) -> StatusError {
|
||||
let mut body = String::new();
|
||||
self.read.read_to_string(&mut body).await.ok();
|
||||
|
||||
StatusError {
|
||||
url: self.url,
|
||||
status_code: self.status_code.as_u16(),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes the response body as JSON
|
||||
pub async fn json<T: DeserializeOwned>(&mut self) -> Result<T, AnyError> {
|
||||
let mut buf = vec![];
|
||||
|
||||
// ideally serde would deserialize a stream, but it does not appear that
|
||||
// is supported. reqwest itself reads and decodes separately like we do here:
|
||||
self.read
|
||||
.read_to_end(&mut buf)
|
||||
.await
|
||||
.map_err(|e| wrap(e, "error reading response"))?;
|
||||
|
||||
let t = serde_json::from_slice(&buf)
|
||||
.map_err(|e| wrap(e, format!("error decoding json from {}", self.url)))?;
|
||||
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// *Very* simple HTTP implementation. In most cases, this will just delegate to
|
||||
/// the request library on the server (i.e. `reqwest`) but it can also be used
|
||||
/// to make update/download requests on the client rather than the server,
|
||||
/// similar to SSH's `remote.SSH.localServerDownload` setting.
|
||||
#[async_trait]
|
||||
pub trait SimpleHttp {
|
||||
async fn make_request(
|
||||
&self,
|
||||
method: &'static str,
|
||||
url: String,
|
||||
) -> Result<SimpleResponse, AnyError>;
|
||||
}
|
||||
|
||||
// Implementation of SimpleHttp that uses a reqwest client.
|
||||
#[derive(Clone)]
|
||||
pub struct ReqwestSimpleHttp {
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl ReqwestSimpleHttp {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: reqwest::ClientBuilder::new()
|
||||
.user_agent(get_default_user_agent())
|
||||
.build()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_client(client: reqwest::Client) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ReqwestSimpleHttp {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SimpleHttp for ReqwestSimpleHttp {
|
||||
async fn make_request(
|
||||
&self,
|
||||
method: &'static str,
|
||||
url: String,
|
||||
) -> Result<SimpleResponse, AnyError> {
|
||||
let res = self
|
||||
.client
|
||||
.request(reqwest::Method::try_from(method).unwrap(), &url)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
Ok(SimpleResponse {
|
||||
status_code: res.status(),
|
||||
headers: res.headers().clone(),
|
||||
url,
|
||||
read: Box::pin(
|
||||
res.bytes_stream()
|
||||
.map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e))
|
||||
.into_async_read()
|
||||
.compat(),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum DelegatedHttpEvent {
|
||||
InitResponse {
|
||||
status_code: u16,
|
||||
headers: Vec<(String, String)>,
|
||||
},
|
||||
Body(Vec<u8>),
|
||||
End,
|
||||
}
|
||||
|
||||
// Handle for a delegated request that allows manually issuing and response.
|
||||
pub struct DelegatedHttpRequest {
|
||||
pub method: &'static str,
|
||||
pub url: String,
|
||||
ch: mpsc::UnboundedSender<DelegatedHttpEvent>,
|
||||
}
|
||||
|
||||
impl DelegatedHttpRequest {
|
||||
pub fn initial_response(&self, status_code: u16, headers: Vec<(String, String)>) {
|
||||
self.ch
|
||||
.send(DelegatedHttpEvent::InitResponse {
|
||||
status_code,
|
||||
headers,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn body(&self, chunk: Vec<u8>) {
|
||||
self.ch.send(DelegatedHttpEvent::Body(chunk)).ok();
|
||||
}
|
||||
|
||||
pub fn end(self) {}
|
||||
}
|
||||
|
||||
impl Drop for DelegatedHttpRequest {
|
||||
fn drop(&mut self) {
|
||||
self.ch.send(DelegatedHttpEvent::End).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of SimpleHttp that allows manually controlling responses.
|
||||
#[derive(Clone)]
|
||||
pub struct DelegatedSimpleHttp {
|
||||
start_request: mpsc::Sender<DelegatedHttpRequest>,
|
||||
log: log::Logger,
|
||||
}
|
||||
|
||||
impl DelegatedSimpleHttp {
|
||||
pub fn new(log: log::Logger) -> (Self, mpsc::Receiver<DelegatedHttpRequest>) {
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
(
|
||||
DelegatedSimpleHttp {
|
||||
log,
|
||||
start_request: tx,
|
||||
},
|
||||
rx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SimpleHttp for DelegatedSimpleHttp {
|
||||
async fn make_request(
|
||||
&self,
|
||||
method: &'static str,
|
||||
url: String,
|
||||
) -> Result<SimpleResponse, AnyError> {
|
||||
trace!(self.log, "making delegated request to {}", url);
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let sent = self
|
||||
.start_request
|
||||
.send(DelegatedHttpRequest {
|
||||
method,
|
||||
url: url.clone(),
|
||||
ch: tx,
|
||||
})
|
||||
.await;
|
||||
|
||||
if sent.is_err() {
|
||||
return Ok(SimpleResponse::generic_error(url)); // sender shut down
|
||||
}
|
||||
|
||||
match rx.recv().await {
|
||||
Some(DelegatedHttpEvent::InitResponse {
|
||||
status_code,
|
||||
headers,
|
||||
}) => {
|
||||
trace!(
|
||||
self.log,
|
||||
"delegated request to {} resulted in status = {}",
|
||||
url,
|
||||
status_code
|
||||
);
|
||||
let mut headers_map = HeaderMap::with_capacity(headers.len());
|
||||
for (k, v) in &headers {
|
||||
if let (Ok(key), Ok(value)) = (
|
||||
HeaderName::from_str(&k.to_lowercase()),
|
||||
HeaderValue::from_str(v),
|
||||
) {
|
||||
headers_map.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SimpleResponse {
|
||||
url,
|
||||
status_code: StatusCode::from_u16(status_code)
|
||||
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
headers: headers_map,
|
||||
read: Box::pin(DelegatedReader::new(rx)),
|
||||
})
|
||||
}
|
||||
Some(DelegatedHttpEvent::End) => Ok(SimpleResponse::generic_error(url)),
|
||||
Some(_) => panic!("expected initresponse as first message from delegated http"),
|
||||
None => Ok(SimpleResponse::generic_error(url)), // sender shut down
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DelegatedReader {
|
||||
receiver: mpsc::UnboundedReceiver<DelegatedHttpEvent>,
|
||||
readbuf: ReadBuffer,
|
||||
}
|
||||
|
||||
impl DelegatedReader {
|
||||
pub fn new(rx: mpsc::UnboundedReceiver<DelegatedHttpEvent>) -> Self {
|
||||
DelegatedReader {
|
||||
readbuf: ReadBuffer::default(),
|
||||
receiver: rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for DelegatedReader {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
if let Some((v, s)) = self.readbuf.take_data() {
|
||||
return self.readbuf.put_data(buf, v, s);
|
||||
}
|
||||
|
||||
match self.receiver.poll_recv(cx) {
|
||||
Poll::Ready(Some(DelegatedHttpEvent::Body(msg))) => self.readbuf.put_data(buf, msg, 0),
|
||||
Poll::Ready(Some(_)) => Poll::Ready(Ok(())), // EOF
|
||||
Poll::Ready(None) => {
|
||||
Poll::Ready(Err(io::Error::new(io::ErrorKind::UnexpectedEof, "EOF")))
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple http implementation that falls back to delegated http if
|
||||
/// making a direct reqwest fails.
|
||||
#[derive(Clone)]
|
||||
pub struct FallbackSimpleHttp {
|
||||
native: ReqwestSimpleHttp,
|
||||
delegated: DelegatedSimpleHttp,
|
||||
}
|
||||
|
||||
impl FallbackSimpleHttp {
|
||||
pub fn new(native: ReqwestSimpleHttp, delegated: DelegatedSimpleHttp) -> Self {
|
||||
FallbackSimpleHttp { native, delegated }
|
||||
}
|
||||
|
||||
pub fn native(&self) -> ReqwestSimpleHttp {
|
||||
self.native.clone()
|
||||
}
|
||||
|
||||
pub fn delegated(&self) -> DelegatedSimpleHttp {
|
||||
self.delegated.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SimpleHttp for FallbackSimpleHttp {
|
||||
async fn make_request(
|
||||
&self,
|
||||
method: &'static str,
|
||||
url: String,
|
||||
) -> Result<SimpleResponse, AnyError> {
|
||||
let r1 = self.native.make_request(method, url.clone()).await;
|
||||
if let Ok(res) = r1 {
|
||||
if !res.status_code.is_server_error() {
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
|
||||
self.delegated.make_request(method, url).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
use std::io;
|
||||
use std::{io, task::Poll};
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
|
||||
|
@ -57,3 +57,41 @@ where
|
|||
|
||||
Ok(bytes_so_far)
|
||||
}
|
||||
|
||||
/// Helper used when converting Future interfaces to poll-based interfaces.
|
||||
/// Stores excess data that can be reused on future polls.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ReadBuffer(Option<(Vec<u8>, usize)>);
|
||||
|
||||
impl ReadBuffer {
|
||||
/// Removes any data stored in the read buffer
|
||||
pub fn take_data(&mut self) -> Option<(Vec<u8>, usize)> {
|
||||
self.0.take()
|
||||
}
|
||||
|
||||
/// Writes as many bytes as possible to the readbuf, stashing any extra.
|
||||
pub fn put_data(
|
||||
&mut self,
|
||||
target: &mut tokio::io::ReadBuf<'_>,
|
||||
bytes: Vec<u8>,
|
||||
start: usize,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
if bytes.is_empty() {
|
||||
self.0 = None;
|
||||
// should not return Ok(), since if nothing is written to the target
|
||||
// it signals EOF. Instead wait for more data from the source.
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
if target.remaining() >= bytes.len() - start {
|
||||
target.put_slice(&bytes[start..]);
|
||||
self.0 = None;
|
||||
} else {
|
||||
let end = start + target.remaining();
|
||||
target.put_slice(&bytes[start..end]);
|
||||
self.0 = Some((bytes, end));
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ lazy_static! {
|
|||
static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 17, 0);
|
||||
}
|
||||
|
||||
const NIXOS_TEST_PATH: &str = "/etc/NIXOS";
|
||||
|
||||
pub struct PreReqChecker {}
|
||||
|
||||
impl Default for PreReqChecker {
|
||||
|
@ -45,13 +47,14 @@ impl PreReqChecker {
|
|||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn verify(&self) -> Result<Platform, AnyError> {
|
||||
let (gnu_a, gnu_b, or_musl) = tokio::join!(
|
||||
let (is_nixos, gnu_a, gnu_b, or_musl) = tokio::join!(
|
||||
check_is_nixos(),
|
||||
check_glibc_version(),
|
||||
check_glibcxx_version(),
|
||||
check_musl_interpreter()
|
||||
);
|
||||
|
||||
if gnu_a.is_ok() && gnu_b.is_ok() {
|
||||
if (gnu_a.is_ok() && gnu_b.is_ok()) || is_nixos {
|
||||
return Ok(if cfg!(target_arch = "x86_64") {
|
||||
Platform::LinuxX64
|
||||
} else if cfg!(target_arch = "armhf") {
|
||||
|
@ -132,6 +135,13 @@ async fn check_glibc_version() -> Result<(), String> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Check for nixos to avoid mandating glibc versions. See:
|
||||
/// https://github.com/microsoft/vscode-remote-release/issues/7129
|
||||
#[allow(dead_code)]
|
||||
async fn check_is_nixos() -> bool {
|
||||
fs::metadata(NIXOS_TEST_PATH).await.is_ok()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn check_glibcxx_version() -> Result<(), String> {
|
||||
let mut libstdc_path: Option<String> = None;
|
||||
|
|
|
@ -2697,14 +2697,7 @@ export class CommandCenter {
|
|||
if (!HEAD) {
|
||||
return;
|
||||
} else if (!HEAD.upstream) {
|
||||
const branchName = HEAD.name;
|
||||
const message = l10n.t('The branch "{0}" has no remote branch. Would you like to publish this branch?', branchName ?? '');
|
||||
const yes = l10n.t('OK');
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, yes);
|
||||
|
||||
if (pick === yes) {
|
||||
await this.publish(repository);
|
||||
}
|
||||
this._push(repository, { pushType: PushType.Push });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToke
|
|||
}
|
||||
|
||||
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
||||
throw new GitError({ message: 'Cancelled' });
|
||||
throw new CancellationError();
|
||||
}
|
||||
|
||||
const disposables: IDisposable[] = [];
|
||||
|
@ -239,7 +239,7 @@ async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToke
|
|||
// noop
|
||||
}
|
||||
|
||||
e(new GitError({ message: 'Cancelled' }));
|
||||
e(new CancellationError());
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -568,12 +568,21 @@ export class Git {
|
|||
}
|
||||
|
||||
const startExec = Date.now();
|
||||
const bufferResult = await exec(child, options.cancellationToken);
|
||||
const durExec = Date.now() - startExec;
|
||||
let bufferResult: IExecutionResult<Buffer>;
|
||||
|
||||
try {
|
||||
bufferResult = await exec(child, options.cancellationToken);
|
||||
} catch (ex) {
|
||||
if (ex instanceof CancellationError) {
|
||||
this.log(`> git ${args.join(' ')} [${Date.now() - startExec}ms] (cancelled)\n`);
|
||||
}
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (options.log !== false) {
|
||||
// command
|
||||
this.log(`> git ${args.join(' ')} [${durExec}ms]\n`);
|
||||
this.log(`> git ${args.join(' ')} [${Date.now() - startExec}ms]\n`);
|
||||
|
||||
// stdout
|
||||
if (bufferResult.stdout.length > 0 && args.find(a => this.commandsToLog.includes(a))) {
|
||||
|
@ -695,45 +704,39 @@ interface GitConfigSection {
|
|||
}
|
||||
|
||||
class GitConfigParser {
|
||||
private static readonly _lineSeparator = /\r?\n/;
|
||||
private static readonly _lineSeparator = /\r?\n/g;
|
||||
|
||||
private static readonly _commentRegex = /^\s*[#;].*/;
|
||||
private static readonly _emptyLineRegex = /^\s*$/;
|
||||
private static readonly _propertyRegex = /^\s*(\w+)\s*=\s*(.*)$/;
|
||||
private static readonly _sectionRegex = /^\s*\[\s*([^\]]+?)\s*(\"[^"]+\")*\]\s*$/;
|
||||
|
||||
static parse(raw: string, sectionName: string): GitConfigSection[] {
|
||||
let section: GitConfigSection | undefined;
|
||||
static parse(raw: string): GitConfigSection[] {
|
||||
const config: { sections: GitConfigSection[] } = { sections: [] };
|
||||
let section: GitConfigSection = { name: 'DEFAULT', properties: {} };
|
||||
|
||||
const addSection = (section?: GitConfigSection) => {
|
||||
if (!section) { return; }
|
||||
config.sections.push(section);
|
||||
};
|
||||
|
||||
for (const configFileLine of raw.split(GitConfigParser._lineSeparator)) {
|
||||
// Ignore empty lines and comments
|
||||
if (GitConfigParser._emptyLineRegex.test(configFileLine) ||
|
||||
GitConfigParser._commentRegex.test(configFileLine)) {
|
||||
continue;
|
||||
}
|
||||
let position = 0;
|
||||
let match: RegExpExecArray | null = null;
|
||||
|
||||
// Section
|
||||
const sectionMatch = configFileLine.match(GitConfigParser._sectionRegex);
|
||||
while (match = GitConfigParser._lineSeparator.exec(raw)) {
|
||||
const line = raw.substring(position, match.index);
|
||||
position = match.index + match[0].length;
|
||||
|
||||
const sectionMatch = line.match(GitConfigParser._sectionRegex);
|
||||
if (sectionMatch?.length === 3) {
|
||||
addSection(section);
|
||||
section = sectionMatch[1] === sectionName ?
|
||||
{ name: sectionMatch[1], subSectionName: sectionMatch[2]?.replaceAll('"', ''), properties: {} } : undefined;
|
||||
section = { name: sectionMatch[1], subSectionName: sectionMatch[2]?.replaceAll('"', ''), properties: {} };
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Properties
|
||||
if (section) {
|
||||
const propertyMatch = configFileLine.match(GitConfigParser._propertyRegex);
|
||||
if (propertyMatch?.length === 3 && !Object.keys(section.properties).includes(propertyMatch[1])) {
|
||||
section.properties[propertyMatch[1]] = propertyMatch[2];
|
||||
}
|
||||
const propertyMatch = line.match(GitConfigParser._propertyRegex);
|
||||
if (propertyMatch?.length === 3 && !Object.keys(section.properties).includes(propertyMatch[1])) {
|
||||
section.properties[propertyMatch[1]] = propertyMatch[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -818,7 +821,7 @@ export interface Submodule {
|
|||
export function parseGitmodules(raw: string): Submodule[] {
|
||||
const result: Submodule[] = [];
|
||||
|
||||
for (const submoduleSection of GitConfigParser.parse(raw, 'submodule')) {
|
||||
for (const submoduleSection of GitConfigParser.parse(raw).filter(s => s.name === 'submodule')) {
|
||||
if (submoduleSection.subSectionName && submoduleSection.properties['path'] && submoduleSection.properties['url']) {
|
||||
result.push({
|
||||
name: submoduleSection.subSectionName,
|
||||
|
@ -834,18 +837,16 @@ export function parseGitmodules(raw: string): Submodule[] {
|
|||
export function parseGitRemotes(raw: string): Remote[] {
|
||||
const remotes: Remote[] = [];
|
||||
|
||||
for (const remoteSection of GitConfigParser.parse(raw, 'remote')) {
|
||||
if (!remoteSection.subSectionName) {
|
||||
continue;
|
||||
for (const remoteSection of GitConfigParser.parse(raw).filter(s => s.name === 'remote')) {
|
||||
if (remoteSection.subSectionName) {
|
||||
remotes.push({
|
||||
name: remoteSection.subSectionName,
|
||||
fetchUrl: remoteSection.properties['url'],
|
||||
pushUrl: remoteSection.properties['pushurl'] ?? remoteSection.properties['url'],
|
||||
// https://github.com/microsoft/vscode/issues/45271
|
||||
isReadOnly: remoteSection.properties['pushurl'] === 'no_push'
|
||||
});
|
||||
}
|
||||
|
||||
remotes.push({
|
||||
name: remoteSection.subSectionName,
|
||||
fetchUrl: remoteSection.properties['url'],
|
||||
pushUrl: remoteSection.properties['pushurl'] ?? remoteSection.properties['url'],
|
||||
// https://github.com/microsoft/vscode/issues/45271
|
||||
isReadOnly: remoteSection.properties['pushurl'] === 'no_push'
|
||||
});
|
||||
}
|
||||
|
||||
return remotes;
|
||||
|
@ -2127,7 +2128,11 @@ export class Repository {
|
|||
.map(([ref]) => ({ name: ref, type: RefType.Head } as Branch));
|
||||
}
|
||||
|
||||
async getRefs(opts?: { sort?: 'alphabetically' | 'committerdate'; contains?: string; pattern?: string; count?: number }): Promise<Ref[]> {
|
||||
async getRefs(opts?: { sort?: 'alphabetically' | 'committerdate'; contains?: string; pattern?: string; count?: number; cancellationToken?: CancellationToken }): Promise<Ref[]> {
|
||||
if (opts?.cancellationToken && opts?.cancellationToken.isCancellationRequested) {
|
||||
throw new CancellationError();
|
||||
}
|
||||
|
||||
const args = ['for-each-ref'];
|
||||
|
||||
if (opts?.count) {
|
||||
|
@ -2148,7 +2153,7 @@ export class Repository {
|
|||
args.push('--contains', opts.contains);
|
||||
}
|
||||
|
||||
const result = await this.exec(args);
|
||||
const result = await this.exec(args, { cancellationToken: opts?.cancellationToken });
|
||||
|
||||
const fn = (line: string): Ref | null => {
|
||||
let match: RegExpExecArray | null;
|
||||
|
|
|
@ -308,11 +308,14 @@ export const enum Operation {
|
|||
Diff = 'Diff',
|
||||
MergeBase = 'MergeBase',
|
||||
Add = 'Add',
|
||||
AddNoProgress = 'AddNoProgress',
|
||||
Remove = 'Remove',
|
||||
RevertFiles = 'RevertFiles',
|
||||
RevertFilesNoProgress = 'RevertFilesNoProgress',
|
||||
Commit = 'Commit',
|
||||
PostCommitCommand = 'PostCommitCommand',
|
||||
Clean = 'Clean',
|
||||
CleanNoProgress = 'CleanNoProgress',
|
||||
Branch = 'Branch',
|
||||
GetBranch = 'GetBranch',
|
||||
GetBranches = 'GetBranches',
|
||||
|
@ -376,7 +379,10 @@ function isReadOnly(operation: Operation): boolean {
|
|||
|
||||
function shouldShowProgress(operation: Operation): boolean {
|
||||
switch (operation) {
|
||||
case Operation.AddNoProgress:
|
||||
case Operation.CleanNoProgress:
|
||||
case Operation.FetchNoProgress:
|
||||
case Operation.RevertFilesNoProgress:
|
||||
case Operation.CheckIgnore:
|
||||
case Operation.GetObjectDetails:
|
||||
case Operation.Show:
|
||||
|
@ -1225,8 +1231,12 @@ export class Repository implements Disposable {
|
|||
}
|
||||
|
||||
async add(resources: Uri[], opts?: { update?: boolean }): Promise<void> {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const optimisticUpdate = config.get<boolean>('optimisticUpdate') === true;
|
||||
const operation = optimisticUpdate ? Operation.AddNoProgress : Operation.Add;
|
||||
|
||||
await this.run(
|
||||
Operation.Add,
|
||||
operation,
|
||||
async () => {
|
||||
await this.repository.add(resources.map(r => r.fsPath), opts);
|
||||
this.closeDiffEditors([], [...resources.map(r => r.fsPath)]);
|
||||
|
@ -1276,8 +1286,12 @@ export class Repository implements Disposable {
|
|||
}
|
||||
|
||||
async revert(resources: Uri[]): Promise<void> {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const optimisticUpdate = config.get<boolean>('optimisticUpdate') === true;
|
||||
const operation = optimisticUpdate ? Operation.RevertFilesNoProgress : Operation.RevertFiles;
|
||||
|
||||
await this.run(
|
||||
Operation.RevertFiles,
|
||||
operation,
|
||||
async () => {
|
||||
await this.repository.revert('HEAD', resources.map(r => r.fsPath));
|
||||
this.closeDiffEditors([...resources.length !== 0 ?
|
||||
|
@ -1387,47 +1401,66 @@ export class Repository implements Disposable {
|
|||
}
|
||||
|
||||
async clean(resources: Uri[]): Promise<void> {
|
||||
await this.run(Operation.Clean, async () => {
|
||||
const toClean: string[] = [];
|
||||
const toCheckout: string[] = [];
|
||||
const submodulesToUpdate: string[] = [];
|
||||
const resourceStates = [...this.workingTreeGroup.resourceStates, ...this.untrackedGroup.resourceStates];
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const optimisticUpdate = config.get<boolean>('optimisticUpdate') === true;
|
||||
const operation = optimisticUpdate ? Operation.CleanNoProgress : Operation.Clean;
|
||||
|
||||
resources.forEach(r => {
|
||||
const fsPath = r.fsPath;
|
||||
await this.run(
|
||||
operation,
|
||||
async () => {
|
||||
const toClean: string[] = [];
|
||||
const toCheckout: string[] = [];
|
||||
const submodulesToUpdate: string[] = [];
|
||||
const resourceStates = [...this.workingTreeGroup.resourceStates, ...this.untrackedGroup.resourceStates];
|
||||
|
||||
for (const submodule of this.submodules) {
|
||||
if (path.join(this.root, submodule.path) === fsPath) {
|
||||
submodulesToUpdate.push(fsPath);
|
||||
resources.forEach(r => {
|
||||
const fsPath = r.fsPath;
|
||||
|
||||
for (const submodule of this.submodules) {
|
||||
if (path.join(this.root, submodule.path) === fsPath) {
|
||||
submodulesToUpdate.push(fsPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const raw = r.toString();
|
||||
const scmResource = find(resourceStates, sr => sr.resourceUri.toString() === raw);
|
||||
|
||||
if (!scmResource) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const raw = r.toString();
|
||||
const scmResource = find(resourceStates, sr => sr.resourceUri.toString() === raw);
|
||||
switch (scmResource.type) {
|
||||
case Status.UNTRACKED:
|
||||
case Status.IGNORED:
|
||||
toClean.push(fsPath);
|
||||
break;
|
||||
|
||||
if (!scmResource) {
|
||||
return;
|
||||
}
|
||||
default:
|
||||
toCheckout.push(fsPath);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
switch (scmResource.type) {
|
||||
case Status.UNTRACKED:
|
||||
case Status.IGNORED:
|
||||
toClean.push(fsPath);
|
||||
break;
|
||||
await this.repository.clean(toClean);
|
||||
await this.repository.checkout('', toCheckout);
|
||||
await this.repository.updateSubmodules(submodulesToUpdate);
|
||||
|
||||
default:
|
||||
toCheckout.push(fsPath);
|
||||
break;
|
||||
}
|
||||
this.closeDiffEditors([], [...toClean, ...toCheckout]);
|
||||
},
|
||||
() => {
|
||||
const resourcePaths = resources.map(r => r.fsPath);
|
||||
|
||||
// Remove resource(s) from working group
|
||||
const workingTreeGroup = this.workingTreeGroup.resourceStates
|
||||
.filter(r => !resourcePaths.includes(r.resourceUri.fsPath));
|
||||
|
||||
// Remove resource(s) from untracked group
|
||||
const untrackedGroup = this.untrackedGroup.resourceStates
|
||||
.filter(r => !resourcePaths.includes(r.resourceUri.fsPath));
|
||||
|
||||
return { workingTreeGroup, untrackedGroup };
|
||||
});
|
||||
|
||||
await this.repository.clean(toClean);
|
||||
await this.repository.checkout('', toCheckout);
|
||||
await this.repository.updateSubmodules(submodulesToUpdate);
|
||||
|
||||
this.closeDiffEditors([], [...toClean, ...toCheckout]);
|
||||
});
|
||||
}
|
||||
|
||||
closeDiffEditors(indexResources: string[] | undefined, workingTreeResources: string[] | undefined, ignoreSetting: boolean = false): void {
|
||||
|
@ -2059,16 +2092,9 @@ export class Repository implements Disposable {
|
|||
this._updateResourceGroupsState(optimisticResourcesGroups);
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git');
|
||||
let sort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder') || 'alphabetically';
|
||||
if (sort !== 'alphabetically' && sort !== 'committerdate') {
|
||||
sort = 'alphabetically';
|
||||
}
|
||||
|
||||
const [HEAD, refs, remotes, submodules, rebaseCommit, mergeInProgress, commitTemplate] =
|
||||
const [HEAD, remotes, submodules, rebaseCommit, mergeInProgress, commitTemplate] =
|
||||
await Promise.all([
|
||||
this.repository.getHEADBranch(),
|
||||
this.repository.getRefs({ sort }),
|
||||
this.repository.getRemotes(),
|
||||
this.repository.getSubmodules(),
|
||||
this.getRebaseCommit(),
|
||||
|
@ -2076,7 +2102,6 @@ export class Repository implements Disposable {
|
|||
this.getInputTemplate()]);
|
||||
|
||||
this._HEAD = HEAD;
|
||||
this._refs = refs!;
|
||||
this._remotes = remotes!;
|
||||
this._submodules = submodules!;
|
||||
this.rebaseCommit = rebaseCommit;
|
||||
|
@ -2084,8 +2109,20 @@ export class Repository implements Disposable {
|
|||
|
||||
this._sourceControl.commitTemplate = commitTemplate;
|
||||
|
||||
// Update resource states based on status data
|
||||
this._updateResourceGroupsState(await this.getStatus(cancellationToken));
|
||||
// Execute cancellable long-running operations
|
||||
const config = workspace.getConfiguration('git');
|
||||
let sort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder') || 'alphabetically';
|
||||
if (sort !== 'alphabetically' && sort !== 'committerdate') {
|
||||
sort = 'alphabetically';
|
||||
}
|
||||
|
||||
const [resourceGroups, refs] =
|
||||
await Promise.all([
|
||||
this.getStatus(cancellationToken),
|
||||
this.repository.getRefs({ sort, cancellationToken })]);
|
||||
|
||||
this._refs = refs!;
|
||||
this._updateResourceGroupsState(resourceGroups);
|
||||
|
||||
this._onDidChangeStatus.fire();
|
||||
}
|
||||
|
|
|
@ -32,14 +32,11 @@
|
|||
"configuration": [
|
||||
{
|
||||
"properties": {
|
||||
"ipynb.experimental.pasteImages.enabled": {
|
||||
"ipynb.pasteImagesAsAttachments.enabled": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%ipynb.experimental.pasteImages.enabled%",
|
||||
"default": false,
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
"markdownDescription": "%ipynb.pasteImagesAsAttachments.enabled%",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"displayName": ".ipynb Support",
|
||||
"description": "Provides basic support for opening and reading Jupyter's .ipynb notebook files",
|
||||
"ipynb.experimental.pasteImages.enabled":"Enable/Disable pasting images into markdown cells within ipynb files. Requires enabling `#editor.experimental.pasteActions.enabled#`."
|
||||
"ipynb.pasteImagesAsAttachments.enabled": "Enable/disable pasting of images into Markdown cells in ipynb notebook files. Pasted images are inserted as attachments to the cell."
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
|
||||
context.subscriptions.push(notebookImagePasteSetup());
|
||||
|
||||
const enabled = vscode.workspace.getConfiguration('ipynb').get('experimental.pasteImages.enabled', false);
|
||||
const enabled = vscode.workspace.getConfiguration('ipynb').get('pasteImagesAsAttachments.enabled', false);
|
||||
if (enabled) {
|
||||
const cleaner = new AttachmentCleaner();
|
||||
context.subscriptions.push(cleaner);
|
||||
|
|
|
@ -15,7 +15,7 @@ class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider {
|
|||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
|
||||
const enabled = vscode.workspace.getConfiguration('ipynb', document).get('experimental.pasteImages.enabled', false);
|
||||
const enabled = vscode.workspace.getConfiguration('ipynb', document).get('pasteImagesAsAttachments.enabled', false);
|
||||
if (!enabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -622,7 +622,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vscode/extension-telemetry": "0.7.0-preview",
|
||||
"dompurify": "^2.3.3",
|
||||
"dompurify": "^2.4.1",
|
||||
"highlight.js": "^11.4.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-front-matter": "^0.2.1",
|
||||
|
@ -640,7 +640,7 @@
|
|||
"@types/vscode-webview": "^1.57.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"vscode-languageserver-types": "^3.17.2",
|
||||
"vscode-markdown-languageservice": "^0.0.0-alpha.10"
|
||||
"vscode-markdown-languageservice": "^0.2.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { Connection, Emitter, FileChangeType, NotebookDocuments, Position, Range, TextDocuments } from 'vscode-languageserver';
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import * as md from 'vscode-markdown-languageservice';
|
||||
import { ContainingDocumentContext, FileWatcherOptions, IFileSystemWatcher } from 'vscode-markdown-languageservice/out/workspace';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { LsConfiguration } from './config';
|
||||
import * as protocol from './protocol';
|
||||
|
@ -99,7 +98,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
private _watcherPool = 0;
|
||||
private readonly _watchers = new Map<number, {
|
||||
readonly resource: URI;
|
||||
readonly options: FileWatcherOptions;
|
||||
readonly options: md.FileWatcherOptions;
|
||||
readonly onDidChange: Emitter<URI>;
|
||||
readonly onDidCreate: Emitter<URI>;
|
||||
readonly onDidDelete: Emitter<URI>;
|
||||
|
@ -364,7 +363,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
return this.connection.sendRequest(protocol.fs_readDirectory, { uri: resource.toString() });
|
||||
}
|
||||
|
||||
getContainingDocument(resource: URI): ContainingDocumentContext | undefined {
|
||||
getContainingDocument(resource: URI): md.ContainingDocumentContext | undefined {
|
||||
if (resource.scheme === Schemes.notebookCell) {
|
||||
const nb = this.notebooks.findNotebookDocumentForCell(resource.toString());
|
||||
if (nb) {
|
||||
|
@ -377,7 +376,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
watchFile(resource: URI, options: FileWatcherOptions): IFileSystemWatcher {
|
||||
watchFile(resource: URI, options: md.FileWatcherOptions): md.IFileSystemWatcher {
|
||||
const id = this._watcherPool++;
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: watchFile', `(${id}) ${resource}`);
|
||||
|
||||
|
|
|
@ -4,10 +4,9 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { disposeAll } from 'vscode-markdown-languageservice/out/util/dispose';
|
||||
import { ResourceMap } from 'vscode-markdown-languageservice/out/util/resourceMap';
|
||||
import { Utils } from 'vscode-uri';
|
||||
import { IDisposable } from '../util/dispose';
|
||||
import { disposeAll, IDisposable } from '../util/dispose';
|
||||
import { ResourceMap } from '../util/resourceMap';
|
||||
import { Schemes } from '../util/schemes';
|
||||
|
||||
type DirWatcherEntry = {
|
||||
|
|
|
@ -80,9 +80,7 @@ export function createUriListSnippet(document: vscode.TextDocument, uris: readon
|
|||
|
||||
const snippet = new vscode.SnippetString();
|
||||
uris.forEach((uri, i) => {
|
||||
const mdPath = dir && dir.scheme === uri.scheme && dir.authority === uri.authority
|
||||
? encodeURI(path.posix.relative(dir.path, uri.path))
|
||||
: uri.toString(false);
|
||||
const mdPath = getMdPath(dir, uri);
|
||||
|
||||
const ext = URI.Utils.extname(uri).toLowerCase().replace('.', '');
|
||||
const insertAsImage = typeof options?.insertAsImage === 'undefined' ? imageFileExtensions.has(ext) : !!options.insertAsImage;
|
||||
|
@ -102,6 +100,21 @@ export function createUriListSnippet(document: vscode.TextDocument, uris: readon
|
|||
return snippet;
|
||||
}
|
||||
|
||||
function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
|
||||
if (dir && dir.scheme === file.scheme && dir.authority === file.authority) {
|
||||
if (file.scheme === Schemes.file) {
|
||||
// On windows, we must use the native `path.resolve` to generate the relative path
|
||||
// so that drive-letters are resolved cast insensitively. However we then want to
|
||||
// convert back to a posix path to insert in to the document.
|
||||
return encodeURI(path.posix.normalize(path.relative(dir.fsPath, file.fsPath)));
|
||||
}
|
||||
|
||||
return encodeURI(path.posix.relative(dir.path, file.path));
|
||||
}
|
||||
|
||||
return file.toString(false);
|
||||
}
|
||||
|
||||
function getDocumentDir(document: vscode.TextDocument): vscode.Uri | undefined {
|
||||
const docUri = getParentDocumentUri(document);
|
||||
if (docUri.scheme === Schemes.untitled) {
|
||||
|
|
|
@ -13,8 +13,6 @@ import { ITextDocument } from './types/textDocument';
|
|||
import { WebviewResourceProvider } from './util/resources';
|
||||
import { isOfScheme, Schemes } from './util/schemes';
|
||||
|
||||
const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;
|
||||
|
||||
/**
|
||||
* Adds begin line index to the output via the 'data-line' data attribute.
|
||||
*/
|
||||
|
@ -189,7 +187,7 @@ export class MarkdownItEngine implements IMdParser {
|
|||
private _tokenizeString(text: string, engine: MarkdownIt) {
|
||||
this._resetSlugCount();
|
||||
|
||||
return engine.parse(text.replace(UNICODE_NEWLINE_REGEX, ''), {});
|
||||
return engine.parse(text, {});
|
||||
}
|
||||
|
||||
private _resetSlugCount(): void {
|
||||
|
|
|
@ -386,10 +386,10 @@ diagnostic-channel@1.1.0:
|
|||
dependencies:
|
||||
semver "^5.3.0"
|
||||
|
||||
dompurify@^2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c"
|
||||
integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg==
|
||||
dompurify@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631"
|
||||
integrity sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==
|
||||
|
||||
emitter-listener@^1.0.1, emitter-listener@^1.1.1:
|
||||
version "1.1.2"
|
||||
|
@ -634,10 +634,10 @@ vscode-languageserver-types@3.17.2, vscode-languageserver-types@^3.17.1, vscode-
|
|||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2"
|
||||
integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==
|
||||
|
||||
vscode-markdown-languageservice@^0.0.0-alpha.10:
|
||||
version "0.0.0-alpha.10"
|
||||
resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.10.tgz#53b69c981eed7fd5efa155ab8c0f169995568681"
|
||||
integrity sha512-rJ85nJ+d45yCz9lBhipavoWXz/vW5FknqqUpLqhe3/2xkrhxt8zcekhSoDepgkKFcTORAFV6g1SnnqxbVhX+uA==
|
||||
vscode-markdown-languageservice@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.2.0.tgz#93e230a1ed826786792e820a6e993e50139a0119"
|
||||
integrity sha512-3Jh7/eN6zEPqfkT6cjL+AwGoQ7euL8RtW3FYf24IfPksz4nAZJcRibRtpSdlCaOKpwEoy/f5Axh94cmWPIGBMw==
|
||||
dependencies:
|
||||
picomatch "^2.3.1"
|
||||
vscode-languageserver-textdocument "^1.0.5"
|
||||
|
|
|
@ -25,8 +25,10 @@ module.exports = withBrowserDefaults({
|
|||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'./env/node': path.resolve(__dirname, 'src/env/browser'),
|
||||
'./authServer': path.resolve(__dirname, 'src/env/browser/authServer'),
|
||||
'./node/crypto': path.resolve(__dirname, 'src/browser/crypto'),
|
||||
'./node/authServer': path.resolve(__dirname, 'src/browser/authServer'),
|
||||
'./node/buffer': path.resolve(__dirname, 'src/browser/buffer'),
|
||||
'./node/fetch': path.resolve(__dirname, 'src/browser/fetch'),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -55,12 +55,7 @@
|
|||
"@types/uuid": "8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": "^5.6.0",
|
||||
"node-fetch": "2.6.7",
|
||||
"randombytes": "~2.1.0",
|
||||
"sha.js": "2.4.11",
|
||||
"stream": "0.0.2",
|
||||
"uuid": "^8.2.0",
|
||||
"@vscode/extension-telemetry": "0.7.0-preview"
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -3,18 +3,16 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as randomBytes from 'randombytes';
|
||||
import * as querystring from 'querystring';
|
||||
import { Buffer } from 'buffer';
|
||||
import * as vscode from 'vscode';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import Logger from './logger';
|
||||
import { isSupportedEnvironment, toBase64UrlEncoding } from './utils';
|
||||
import { sha256 } from './env/node/sha256';
|
||||
import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage';
|
||||
import { LoopbackAuthServer } from './authServer';
|
||||
import * as querystring from 'querystring';
|
||||
import path = require('path');
|
||||
import Logger from './logger';
|
||||
import { isSupportedEnvironment } from './utils';
|
||||
import { generateCodeChallenge, generateCodeVerifier, randomUUID } from './cryptoUtils';
|
||||
import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage';
|
||||
import { LoopbackAuthServer } from './node/authServer';
|
||||
import { base64Decode } from './node/buffer';
|
||||
import { fetching } from './node/fetch';
|
||||
|
||||
const redirectUrl = 'https://vscode.dev/redirect';
|
||||
const loginEndpointUrl = 'https://login.microsoftonline.com/';
|
||||
|
@ -295,8 +293,8 @@ export class AzureActiveDirectoryService {
|
|||
}
|
||||
|
||||
private async createSessionWithLocalServer(scopeData: IScopeData) {
|
||||
const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64'));
|
||||
const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier));
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
||||
const qs = new URLSearchParams({
|
||||
response_type: 'code',
|
||||
response_mode: 'query',
|
||||
|
@ -328,15 +326,15 @@ export class AzureActiveDirectoryService {
|
|||
|
||||
private async createSessionWithoutLocalServer(scopeData: IScopeData): Promise<vscode.AuthenticationSession> {
|
||||
let callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`));
|
||||
const nonce = randomBytes(16).toString('base64');
|
||||
const nonce = generateCodeVerifier();
|
||||
const callbackQuery = new URLSearchParams(callbackUri.query);
|
||||
callbackQuery.set('nonce', encodeURIComponent(nonce));
|
||||
callbackUri = callbackUri.with({
|
||||
query: callbackQuery.toString()
|
||||
});
|
||||
const state = encodeURIComponent(callbackUri.toString(true));
|
||||
const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64'));
|
||||
const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier));
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
||||
const signInUrl = `${loginEndpointUrl}${scopeData.tenant}/oauth2/v2.0/authorize`;
|
||||
const oauthStartQuery = new URLSearchParams({
|
||||
response_type: 'code',
|
||||
|
@ -467,10 +465,10 @@ export class AzureActiveDirectoryService {
|
|||
|
||||
try {
|
||||
if (json.id_token) {
|
||||
claims = JSON.parse(Buffer.from(json.id_token.split('.')[1], 'base64').toString());
|
||||
claims = JSON.parse(base64Decode(json.id_token.split('.')[1]));
|
||||
} else {
|
||||
Logger.info('Attempting to parse access_token instead since no id_token was included in the response.');
|
||||
claims = JSON.parse(Buffer.from(json.access_token.split('.')[1], 'base64').toString());
|
||||
claims = JSON.parse(base64Decode(json.access_token.split('.')[1]));
|
||||
}
|
||||
} catch (e) {
|
||||
throw e;
|
||||
|
@ -491,7 +489,7 @@ export class AzureActiveDirectoryService {
|
|||
idToken: json.id_token,
|
||||
refreshToken: json.refresh_token,
|
||||
scope: scopeData.scopeStr,
|
||||
sessionId: existingId || `${id}/${uuid()}`,
|
||||
sessionId: existingId || `${id}/${randomUUID()}`,
|
||||
account: {
|
||||
label,
|
||||
id
|
||||
|
@ -739,10 +737,10 @@ export class AzureActiveDirectoryService {
|
|||
let attempts = 0;
|
||||
while (attempts <= 3) {
|
||||
attempts++;
|
||||
let result: Response | undefined;
|
||||
let result;
|
||||
let errorMessage: string | undefined;
|
||||
try {
|
||||
result = await fetch(endpoint, {
|
||||
result = await fetching(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export async function sha256(s: string | Uint8Array): Promise<string> {
|
||||
const createHash = require('sha.js');
|
||||
return createHash('sha256').update(s).digest('base64');
|
||||
export function base64Encode(text: string): string {
|
||||
return btoa(text);
|
||||
}
|
||||
|
||||
export function base64Decode(text: string): string {
|
||||
const data = atob(text);
|
||||
return data;
|
||||
}
|
|
@ -3,8 +3,4 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export async function sha256(s: string | Uint8Array): Promise<string> {
|
||||
return (require('crypto')).createHash('sha256').update(s).digest('base64');
|
||||
}
|
||||
export const crypto = globalThis.crypto;
|
6
extensions/microsoft-authentication/src/browser/fetch.ts
Normal file
6
extensions/microsoft-authentication/src/browser/fetch.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const fetching = fetch;
|
45
extensions/microsoft-authentication/src/cryptoUtils.ts
Normal file
45
extensions/microsoft-authentication/src/cryptoUtils.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { base64Encode } from './node/buffer';
|
||||
import { crypto } from './node/crypto';
|
||||
|
||||
export function randomUUID() {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
function dec2hex(dec: number): string {
|
||||
return ('0' + dec.toString(16)).slice(-2);
|
||||
}
|
||||
|
||||
export function generateCodeVerifier(): string {
|
||||
const array = new Uint32Array(56 / 2);
|
||||
crypto.getRandomValues(array);
|
||||
return Array.from(array, dec2hex).join('');
|
||||
}
|
||||
|
||||
function sha256(plain: string | undefined) {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(plain);
|
||||
return crypto.subtle.digest('SHA-256', data);
|
||||
}
|
||||
|
||||
function base64urlencode(a: ArrayBuffer) {
|
||||
let str = '';
|
||||
const bytes = new Uint8Array(a);
|
||||
const len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
str += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return base64Encode(str)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, '');
|
||||
}
|
||||
|
||||
export async function generateCodeChallenge(v: string) {
|
||||
const hashed = await sha256(v);
|
||||
const base64encoded = base64urlencode(hashed);
|
||||
return base64encoded;
|
||||
}
|
12
extensions/microsoft-authentication/src/node/buffer.ts
Normal file
12
extensions/microsoft-authentication/src/node/buffer.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export function base64Encode(text: string): string {
|
||||
return Buffer.from(text, 'binary').toString('base64');
|
||||
}
|
||||
|
||||
export function base64Decode(text: string): string {
|
||||
return Buffer.from(text, 'base64').toString('utf8');
|
||||
}
|
7
extensions/microsoft-authentication/src/node/crypto.ts
Normal file
7
extensions/microsoft-authentication/src/node/crypto.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as nodeCrypto from 'crypto';
|
||||
|
||||
export const crypto: Crypto = nodeCrypto.webcrypto as any;
|
7
extensions/microsoft-authentication/src/node/fetch.ts
Normal file
7
extensions/microsoft-authentication/src/node/fetch.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
export const fetching = fetch;
|
|
@ -4,10 +4,6 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
import { env, UIKind, Uri } from 'vscode';
|
||||
|
||||
export function toBase64UrlEncoding(base64string: string) {
|
||||
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding
|
||||
}
|
||||
|
||||
const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1'];
|
||||
function isLocalhost(uri: Uri): boolean {
|
||||
if (!/^https?$/i.test(uri.scheme)) {
|
||||
|
|
|
@ -297,19 +297,6 @@ asynckit@^0.4.0:
|
|||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
||||
|
||||
base64-js@^1.0.2:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||
|
||||
buffer@^5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
||||
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
cls-hooked@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908"
|
||||
|
@ -351,11 +338,6 @@ diagnostic-channel@1.1.0:
|
|||
dependencies:
|
||||
semver "^5.3.0"
|
||||
|
||||
emitter-component@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6"
|
||||
integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=
|
||||
|
||||
emitter-listener@^1.0.1, emitter-listener@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
|
||||
|
@ -381,16 +363,6 @@ form-data@^4.0.0:
|
|||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
|
||||
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
|
||||
|
||||
inherits@^2.0.1:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
mime-db@1.44.0:
|
||||
version "1.44.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
|
||||
|
@ -430,28 +402,11 @@ querystringify@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
randombytes@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
|
||||
|
||||
safe-buffer@^5.0.1:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
||||
|
||||
safe-buffer@^5.1.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
sax@>=0.6.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
|
@ -462,14 +417,6 @@ semver@^5.3.0, semver@^5.4.1:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
sha.js@2.4.11:
|
||||
version "2.4.11"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
|
||||
dependencies:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
shimmer@^1.1.0, shimmer@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
|
||||
|
@ -480,13 +427,6 @@ stack-chain@^1.3.7:
|
|||
resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285"
|
||||
integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==
|
||||
|
||||
stream@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef"
|
||||
integrity sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8=
|
||||
dependencies:
|
||||
emitter-component "^1.1.1"
|
||||
|
||||
tough-cookie@^4.0.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
|
||||
|
@ -525,11 +465,6 @@ url-parse@^1.5.3:
|
|||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
uuid@^8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e"
|
||||
integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==
|
||||
|
||||
uuid@^8.3.0:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
|
|
|
@ -409,7 +409,7 @@
|
|||
// "activityBar.foreground": "",
|
||||
// "activityBarBadge.background": "",
|
||||
// "activityBarBadge.foreground": "",
|
||||
"activityBarItem.settingsProfilesBackground": "#082877",
|
||||
"activityBarItem.profilesBackground": "#082877",
|
||||
|
||||
// Workbench: Panel
|
||||
// "panel.background": "",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Light (Visual Studio)",
|
||||
"colors": {
|
||||
"activityBarItem.settingsProfilesBackground": "#4d4d4d",
|
||||
"activityBarItem.profilesBackground": "#4d4d4d",
|
||||
"editor.background": "#FFFFFF",
|
||||
"editor.foreground": "#000000",
|
||||
"editor.inactiveSelectionBackground": "#E5EBF1",
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"ports.iconRunningProcessForeground": "#369432",
|
||||
"activityBar.background": "#221a0f",
|
||||
"activityBar.foreground": "#d3af86",
|
||||
"activityBarItem.settingsProfilesBackground": "#47351d",
|
||||
"activityBarItem.profilesBackground": "#47351d",
|
||||
"sideBar.background": "#362712",
|
||||
"menu.background": "#362712",
|
||||
"menu.foreground": "#CCCCCC",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"statusBar.noFolderBackground": "#001126",
|
||||
"statusBar.debuggingBackground": "#001126",
|
||||
"activityBar.background": "#001733",
|
||||
"activityBarItem.settingsProfilesBackground": "#003271",
|
||||
"activityBarItem.profilesBackground": "#003271",
|
||||
"progressBar.background": "#bbdaffcc",
|
||||
"badge.background": "#bbdaffcc",
|
||||
"badge.foreground": "#001733",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.74.0",
|
||||
"distro": "afbf11bcff4e2c6daeaf653c501d727b6970d50c",
|
||||
"distro": "f9046f24c03eab03550535a58f6d5be90106b45f",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
|
4
src/bootstrap-window.js
vendored
4
src/bootstrap-window.js
vendored
|
@ -175,11 +175,11 @@
|
|||
}
|
||||
|
||||
// Actually require the main module as specified
|
||||
require(modulePaths, async result => {
|
||||
require(modulePaths, async firstModule => {
|
||||
try {
|
||||
|
||||
// Callback only after process environment is resolved
|
||||
const callbackResult = resultCallback(result, configuration);
|
||||
const callbackResult = resultCallback(firstModule, configuration);
|
||||
if (callbackResult instanceof Promise) {
|
||||
await callbackResult;
|
||||
|
||||
|
|
4
src/typings/require.d.ts
vendored
4
src/typings/require.d.ts
vendored
|
@ -49,8 +49,8 @@ interface NodeRequire {
|
|||
config(data: any): any;
|
||||
onError: Function;
|
||||
__$__nodeRequire<T>(moduleName: string): T;
|
||||
getStats(): ReadonlyArray<LoaderEvent>;
|
||||
hasDependencyCycle(): boolean;
|
||||
getStats?(): ReadonlyArray<LoaderEvent>;
|
||||
hasDependencyCycle?(): boolean;
|
||||
define(amdModuleId: string, dependencies: string[], callback: (...args: any[]) => any): any;
|
||||
}
|
||||
|
||||
|
|
|
@ -1199,7 +1199,7 @@ export function asCSSUrl(uri: URI | null | undefined): string {
|
|||
if (!uri) {
|
||||
return `url('')`;
|
||||
}
|
||||
return `url('${FileAccess.asBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`;
|
||||
return `url('${FileAccess.uriToBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`;
|
||||
}
|
||||
|
||||
export function asCSSPropertyValue(value: string) {
|
||||
|
|
|
@ -126,7 +126,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
// and because of that special rewriting needs to be done
|
||||
// so that the URI uses a protocol that's understood by
|
||||
// browsers (like http or https)
|
||||
return FileAccess.asBrowserUri(uri).toString(true);
|
||||
return FileAccess.uriToBrowserUri(uri).toString(true);
|
||||
}
|
||||
if (!uri) {
|
||||
return href;
|
||||
|
|
|
@ -14,7 +14,6 @@ import { Color } from 'vs/base/common/color';
|
|||
import { Emitter, Event as BaseEvent } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { localize } from 'vs/nls';
|
||||
import 'vs/css!./button';
|
||||
|
||||
|
@ -24,22 +23,28 @@ export interface IButtonOptions extends IButtonStyles {
|
|||
readonly secondary?: boolean;
|
||||
}
|
||||
|
||||
export type CSSValueString = string;
|
||||
|
||||
export interface IButtonStyles {
|
||||
buttonBackground?: Color;
|
||||
buttonHoverBackground?: Color;
|
||||
buttonForeground?: Color;
|
||||
buttonSeparator?: Color;
|
||||
buttonSecondaryBackground?: Color;
|
||||
buttonSecondaryHoverBackground?: Color;
|
||||
buttonSecondaryForeground?: Color;
|
||||
buttonBorder?: Color;
|
||||
readonly buttonBackground?: CSSValueString;
|
||||
readonly buttonHoverBackground?: CSSValueString;
|
||||
readonly buttonForeground?: CSSValueString;
|
||||
readonly buttonSeparator?: CSSValueString;
|
||||
readonly buttonSecondaryBackground?: CSSValueString;
|
||||
readonly buttonSecondaryHoverBackground?: CSSValueString;
|
||||
readonly buttonSecondaryForeground?: CSSValueString;
|
||||
readonly buttonBorder?: CSSValueString;
|
||||
}
|
||||
|
||||
const defaultOptions: IButtonStyles = {
|
||||
buttonBackground: Color.fromHex('#0E639C'),
|
||||
buttonHoverBackground: Color.fromHex('#006BB3'),
|
||||
buttonSeparator: Color.white,
|
||||
buttonForeground: Color.white
|
||||
export const defaultOptions: IButtonStyles = {
|
||||
buttonBackground: '#0E639C',
|
||||
buttonHoverBackground: '#006BB3',
|
||||
buttonSeparator: Color.white.toString(),
|
||||
buttonForeground: Color.white.toString(),
|
||||
buttonBorder: undefined,
|
||||
buttonSecondaryBackground: undefined,
|
||||
buttonSecondaryForeground: undefined,
|
||||
buttonSecondaryHoverBackground: undefined
|
||||
};
|
||||
|
||||
export interface IButton extends IDisposable {
|
||||
|
@ -48,7 +53,6 @@ export interface IButton extends IDisposable {
|
|||
label: string;
|
||||
icon: CSSIcon;
|
||||
enabled: boolean;
|
||||
style(styles: IButtonStyles): void;
|
||||
focus(): void;
|
||||
hasFocus(): boolean;
|
||||
}
|
||||
|
@ -62,40 +66,31 @@ export class Button extends Disposable implements IButton {
|
|||
protected _element: HTMLElement;
|
||||
protected options: IButtonOptions;
|
||||
|
||||
private buttonBackground: Color | undefined;
|
||||
private buttonHoverBackground: Color | undefined;
|
||||
private buttonForeground: Color | undefined;
|
||||
private buttonSecondaryBackground: Color | undefined;
|
||||
private buttonSecondaryHoverBackground: Color | undefined;
|
||||
private buttonSecondaryForeground: Color | undefined;
|
||||
private buttonBorder: Color | undefined;
|
||||
|
||||
private _onDidClick = this._register(new Emitter<Event>());
|
||||
get onDidClick(): BaseEvent<Event> { return this._onDidClick.event; }
|
||||
|
||||
private focusTracker: IFocusTracker;
|
||||
|
||||
constructor(container: HTMLElement, options?: IButtonOptions) {
|
||||
constructor(container: HTMLElement, options: IButtonOptions) {
|
||||
super();
|
||||
|
||||
this.options = options || Object.create(null);
|
||||
mixin(this.options, defaultOptions, false);
|
||||
|
||||
this.buttonForeground = this.options.buttonForeground;
|
||||
this.buttonBackground = this.options.buttonBackground;
|
||||
this.buttonHoverBackground = this.options.buttonHoverBackground;
|
||||
|
||||
this.buttonSecondaryForeground = this.options.buttonSecondaryForeground;
|
||||
this.buttonSecondaryBackground = this.options.buttonSecondaryBackground;
|
||||
this.buttonSecondaryHoverBackground = this.options.buttonSecondaryHoverBackground;
|
||||
|
||||
this.buttonBorder = this.options.buttonBorder;
|
||||
this.options = options;
|
||||
|
||||
this._element = document.createElement('a');
|
||||
this._element.classList.add('monaco-button');
|
||||
this._element.tabIndex = 0;
|
||||
this._element.setAttribute('role', 'button');
|
||||
|
||||
const background = options.secondary ? options.buttonSecondaryBackground : options.buttonBackground;
|
||||
const foreground = options.secondary ? options.buttonSecondaryForeground : options.buttonForeground;
|
||||
const border = options.buttonBorder;
|
||||
|
||||
this._element.style.color = foreground || '';
|
||||
this._element.style.backgroundColor = background || '';
|
||||
if (border) {
|
||||
this._element.style.border = `1px solid ${border}`;
|
||||
}
|
||||
|
||||
container.appendChild(this._element);
|
||||
|
||||
this._register(Gesture.addTarget(this._element));
|
||||
|
@ -129,65 +124,29 @@ export class Button extends Disposable implements IButton {
|
|||
|
||||
this._register(addDisposableListener(this._element, EventType.MOUSE_OVER, e => {
|
||||
if (!this._element.classList.contains('disabled')) {
|
||||
this.setHoverBackground();
|
||||
this.updateBackground(true);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this._element, EventType.MOUSE_OUT, e => {
|
||||
this.applyStyles(); // restore standard styles
|
||||
this.updateBackground(false); // restore standard styles
|
||||
}));
|
||||
|
||||
// Also set hover background when button is focused for feedback
|
||||
this.focusTracker = this._register(trackFocus(this._element));
|
||||
this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.setHoverBackground(); } }));
|
||||
this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.applyStyles(); } }));
|
||||
|
||||
this.applyStyles();
|
||||
this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.updateBackground(true); } }));
|
||||
this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.updateBackground(false); } }));
|
||||
}
|
||||
|
||||
private setHoverBackground(): void {
|
||||
let hoverBackground;
|
||||
private updateBackground(hover: boolean): void {
|
||||
let background;
|
||||
if (this.options.secondary) {
|
||||
hoverBackground = this.buttonSecondaryHoverBackground ? this.buttonSecondaryHoverBackground.toString() : null;
|
||||
background = hover ? this.options.buttonSecondaryHoverBackground : this.options.buttonSecondaryBackground;
|
||||
} else {
|
||||
hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
|
||||
background = hover ? this.options.buttonHoverBackground : this.options.buttonBackground;
|
||||
}
|
||||
if (hoverBackground) {
|
||||
this._element.style.backgroundColor = hoverBackground;
|
||||
}
|
||||
}
|
||||
|
||||
style(styles: IButtonStyles): void {
|
||||
this.buttonForeground = styles.buttonForeground;
|
||||
this.buttonBackground = styles.buttonBackground;
|
||||
this.buttonHoverBackground = styles.buttonHoverBackground;
|
||||
this.buttonSecondaryForeground = styles.buttonSecondaryForeground;
|
||||
this.buttonSecondaryBackground = styles.buttonSecondaryBackground;
|
||||
this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground;
|
||||
this.buttonBorder = styles.buttonBorder;
|
||||
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
private applyStyles(): void {
|
||||
if (this._element) {
|
||||
let background, foreground;
|
||||
if (this.options.secondary) {
|
||||
foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : '';
|
||||
background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : '';
|
||||
} else {
|
||||
foreground = this.buttonForeground ? this.buttonForeground.toString() : '';
|
||||
background = this.buttonBackground ? this.buttonBackground.toString() : '';
|
||||
}
|
||||
|
||||
const border = this.buttonBorder ? this.buttonBorder.toString() : '';
|
||||
|
||||
this._element.style.color = foreground;
|
||||
if (background) {
|
||||
this._element.style.backgroundColor = background;
|
||||
|
||||
this._element.style.borderWidth = border ? '1px' : '';
|
||||
this._element.style.borderStyle = border ? 'solid' : '';
|
||||
this._element.style.borderColor = border;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,6 +252,21 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
|||
this.separatorContainer.appendChild(this.separator);
|
||||
this.element.appendChild(this.separatorContainer);
|
||||
|
||||
// Separator styles
|
||||
const border = options.buttonBorder;
|
||||
if (border) {
|
||||
this.separatorContainer.style.borderTopWidth = '1px';
|
||||
this.separatorContainer.style.borderTopStyle = 'solid';
|
||||
this.separatorContainer.style.borderTopColor = border;
|
||||
|
||||
this.separatorContainer.style.borderBottomWidth = '1px';
|
||||
this.separatorContainer.style.borderBottomStyle = 'solid';
|
||||
this.separatorContainer.style.borderBottomColor = border;
|
||||
}
|
||||
this.separatorContainer.style.backgroundColor = options.buttonBackground?.toString() ?? '';
|
||||
this.separator.style.backgroundColor = options.buttonSeparator?.toString() ?? '';
|
||||
|
||||
|
||||
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true }));
|
||||
this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...');
|
||||
this.dropdownButton.element.setAttribute('aria-haspopup', 'true');
|
||||
|
@ -330,25 +304,6 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
|||
return this.button.enabled;
|
||||
}
|
||||
|
||||
style(styles: IButtonStyles): void {
|
||||
this.button.style(styles);
|
||||
this.dropdownButton.style(styles);
|
||||
|
||||
// Separator
|
||||
const border = styles.buttonBorder ? styles.buttonBorder.toString() : '';
|
||||
|
||||
this.separatorContainer.style.borderTopWidth = border ? '1px' : '';
|
||||
this.separatorContainer.style.borderTopStyle = border ? 'solid' : '';
|
||||
this.separatorContainer.style.borderTopColor = border;
|
||||
|
||||
this.separatorContainer.style.borderBottomWidth = border ? '1px' : '';
|
||||
this.separatorContainer.style.borderBottomStyle = border ? 'solid' : '';
|
||||
this.separatorContainer.style.borderBottomColor = border;
|
||||
|
||||
this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? '';
|
||||
this.separator.style.backgroundColor = styles.buttonSeparator?.toString() ?? '';
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.button.focus();
|
||||
}
|
||||
|
@ -363,7 +318,7 @@ export class ButtonWithDescription extends Button implements IButtonWithDescript
|
|||
private _labelElement: HTMLElement;
|
||||
private _descriptionElement: HTMLElement;
|
||||
|
||||
constructor(container: HTMLElement, options?: IButtonOptions) {
|
||||
constructor(container: HTMLElement, options: IButtonOptions) {
|
||||
super(container, options);
|
||||
|
||||
this._element.classList.add('monaco-description-button');
|
||||
|
@ -412,13 +367,13 @@ export class ButtonBar extends Disposable {
|
|||
return this._buttons;
|
||||
}
|
||||
|
||||
addButton(options?: IButtonOptions): IButton {
|
||||
addButton(options: IButtonOptions): IButton {
|
||||
const button = this._register(new Button(this.container, options));
|
||||
this.pushButton(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
addButtonWithDescription(options?: IButtonOptions): IButtonWithDescription {
|
||||
addButtonWithDescription(options: IButtonOptions): IButtonWithDescription {
|
||||
const button = this._register(new ButtonWithDescription(this.container, options));
|
||||
this.pushButton(button);
|
||||
return button;
|
||||
|
|
|
@ -427,8 +427,6 @@ export class Dialog extends Disposable {
|
|||
this.element.style.backgroundColor = bgColor?.toString() ?? '';
|
||||
this.element.style.border = border;
|
||||
|
||||
this.buttonBar?.buttons.forEach(button => button.style(style));
|
||||
|
||||
this.checkbox?.style(style);
|
||||
|
||||
if (fgColor && bgColor) {
|
||||
|
|
|
@ -40,7 +40,10 @@ export abstract class LoaderStats {
|
|||
map.set(stat.detail, duration + stat.timestamp);
|
||||
}
|
||||
|
||||
const stats = require.getStats().slice(0).sort((a, b) => a.timestamp - b.timestamp);
|
||||
let stats: readonly LoaderEvent[] = [];
|
||||
if (typeof require.getStats === 'function') {
|
||||
stats = require.getStats().slice(0).sort((a, b) => a.timestamp - b.timestamp);
|
||||
}
|
||||
|
||||
for (const stat of stats) {
|
||||
switch (stat.type) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
|
@ -92,3 +93,17 @@ export class VSDataTransfer {
|
|||
return mimeType.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const UriList = Object.freeze({
|
||||
// http://amundsen.com/hypermedia/urilist/
|
||||
create: (entries: ReadonlyArray<string | URI>): string => {
|
||||
return distinct(entries.map(x => x.toString())).join('\r\n');
|
||||
},
|
||||
split: (str: string): string[] => {
|
||||
return str.split('\r\n');
|
||||
},
|
||||
parse: (str: string): string[] => {
|
||||
return UriList.split(str).filter(value => !value.startsWith('#'));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -170,6 +170,22 @@ class RemoteAuthoritiesImpl {
|
|||
|
||||
export const RemoteAuthorities = new RemoteAuthoritiesImpl();
|
||||
|
||||
/**
|
||||
* A string pointing to a path inside the app. It should not begin with ./ or ../
|
||||
*/
|
||||
export type AppResourcePath = (
|
||||
`a${string}` | `b${string}` | `c${string}` | `d${string}` | `e${string}` | `f${string}`
|
||||
| `g${string}` | `h${string}` | `i${string}` | `j${string}` | `k${string}` | `l${string}`
|
||||
| `m${string}` | `n${string}` | `o${string}` | `p${string}` | `q${string}` | `r${string}`
|
||||
| `s${string}` | `t${string}` | `u${string}` | `v${string}` | `w${string}` | `x${string}`
|
||||
| `y${string}` | `z${string}`
|
||||
);
|
||||
|
||||
export const builtinExtensionsPath: AppResourcePath = 'vs/../../extensions';
|
||||
export const nodeModulesPath: AppResourcePath = 'vs/../../node_modules';
|
||||
export const nodeModulesAsarPath: AppResourcePath = 'vs/../../node_modules.asar';
|
||||
export const nodeModulesAsarUnpackedPath: AppResourcePath = 'vs/../../node_modules.asar.unpacked';
|
||||
|
||||
class FileAccessImpl {
|
||||
|
||||
private static readonly FALLBACK_AUTHORITY = 'vscode-app';
|
||||
|
@ -180,11 +196,18 @@ class FileAccessImpl {
|
|||
*
|
||||
* **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context.
|
||||
*/
|
||||
asBrowserUri(uri: URI): URI;
|
||||
asBrowserUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;
|
||||
asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
asBrowserUri(resourcePath: AppResourcePath | ''): URI {
|
||||
const uri = this.toUri(resourcePath, require);
|
||||
return this.uriToBrowserUri(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URI to use in contexts where the browser is responsible
|
||||
* for loading (e.g. fetch()) or when used within the DOM.
|
||||
*
|
||||
* **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context.
|
||||
*/
|
||||
uriToBrowserUri(uri: URI): URI {
|
||||
// Handle remote URIs via `RemoteAuthorities`
|
||||
if (uri.scheme === Schemas.vscodeRemote) {
|
||||
return RemoteAuthorities.rewrite(uri);
|
||||
|
@ -220,11 +243,16 @@ class FileAccessImpl {
|
|||
* Returns the `file` URI to use in contexts where node.js
|
||||
* is responsible for loading.
|
||||
*/
|
||||
asFileUri(uri: URI): URI;
|
||||
asFileUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;
|
||||
asFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
asFileUri(resourcePath: AppResourcePath | ''): URI {
|
||||
const uri = this.toUri(resourcePath, require);
|
||||
return this.uriToFileUri(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `file` URI to use in contexts where node.js
|
||||
* is responsible for loading.
|
||||
*/
|
||||
uriToFileUri(uri: URI): URI {
|
||||
// Only convert the URI if it is `vscode-file:` scheme
|
||||
if (uri.scheme === Schemas.vscodeFileResource) {
|
||||
return uri.with({
|
||||
|
@ -241,12 +269,12 @@ class FileAccessImpl {
|
|||
return uri;
|
||||
}
|
||||
|
||||
private toUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
private toUri(uriOrModule: URI | string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI {
|
||||
if (URI.isUri(uriOrModule)) {
|
||||
return uriOrModule;
|
||||
}
|
||||
|
||||
return URI.parse(moduleIdToUrl!.toUrl(uriOrModule));
|
||||
return URI.parse(moduleIdToUrl.toUrl(uriOrModule));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
|||
// The cpu usage value reported on Linux is the average over the process lifetime,
|
||||
// recalculate the usage over a one second interval
|
||||
// JSON.stringify is needed to escape spaces, https://github.com/nodejs/node/issues/6803
|
||||
let cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/cpuUsage.sh', require).fsPath);
|
||||
let cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/cpuUsage.sh').fsPath);
|
||||
cmd += ' ' + pids.join(' ');
|
||||
|
||||
exec(cmd, {}, (err, stdout, stderr) => {
|
||||
|
@ -226,7 +226,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
|||
if (process.platform !== 'linux') {
|
||||
reject(err || new Error(stderr.toString()));
|
||||
} else {
|
||||
const cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/ps.sh', require).fsPath);
|
||||
const cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/ps.sh').fsPath);
|
||||
exec(cmd, {}, (err, stdout, stderr) => {
|
||||
if (err || stderr) {
|
||||
reject(err || new Error(stderr.toString()));
|
||||
|
|
|
@ -1295,14 +1295,14 @@ export class QuickInputController extends Disposable {
|
|||
const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") });
|
||||
|
||||
const okContainer = dom.append(headerContainer, $('.quick-input-action'));
|
||||
const ok = new Button(okContainer);
|
||||
const ok = new Button(okContainer, this.styles.button);
|
||||
ok.label = localize('ok', "OK");
|
||||
this._register(ok.onDidClick(e => {
|
||||
this.onDidAcceptEmitter.fire();
|
||||
}));
|
||||
|
||||
const customButtonContainer = dom.append(headerContainer, $('.quick-input-action'));
|
||||
const customButton = new Button(customButtonContainer);
|
||||
const customButton = new Button(customButtonContainer, this.styles.button);
|
||||
customButton.label = localize('custom', "Custom");
|
||||
this._register(customButton.onDidClick(e => {
|
||||
this.onDidCustomEmitter.fire();
|
||||
|
@ -1825,8 +1825,6 @@ export class QuickInputController extends Disposable {
|
|||
this.ui.container.style.boxShadow = widgetShadow ? `0 0 8px 2px ${widgetShadow}` : '';
|
||||
this.ui.inputBox.style(this.styles.inputBox);
|
||||
this.ui.count.style(this.styles.countBadge);
|
||||
this.ui.ok.style(this.styles.button);
|
||||
this.ui.customButton.style(this.styles.button);
|
||||
this.ui.list.style(this.styles.list);
|
||||
|
||||
const content: string[] = [];
|
||||
|
|
|
@ -15,55 +15,55 @@ suite('network', () => {
|
|||
|
||||
// asCodeUri() & asFileUri(): simple, without authority
|
||||
let originalFileUri = URI.file('network.test.ts');
|
||||
let browserUri = FileAccess.asBrowserUri(originalFileUri);
|
||||
let browserUri = FileAccess.uriToBrowserUri(originalFileUri);
|
||||
assert.ok(browserUri.authority.length > 0);
|
||||
let fileUri = FileAccess.asFileUri(browserUri);
|
||||
let fileUri = FileAccess.uriToFileUri(browserUri);
|
||||
assert.strictEqual(fileUri.authority.length, 0);
|
||||
assert(isEqual(originalFileUri, fileUri));
|
||||
|
||||
// asCodeUri() & asFileUri(): with authority
|
||||
originalFileUri = URI.file('network.test.ts').with({ authority: 'test-authority' });
|
||||
browserUri = FileAccess.asBrowserUri(originalFileUri);
|
||||
browserUri = FileAccess.uriToBrowserUri(originalFileUri);
|
||||
assert.strictEqual(browserUri.authority, originalFileUri.authority);
|
||||
fileUri = FileAccess.asFileUri(browserUri);
|
||||
fileUri = FileAccess.uriToFileUri(browserUri);
|
||||
assert(isEqual(originalFileUri, fileUri));
|
||||
});
|
||||
|
||||
(isWeb ? test.skip : test)('FileAccess: moduleId (native)', () => {
|
||||
const browserUri = FileAccess.asBrowserUri('vs/base/test/node/network.test', require);
|
||||
const browserUri = FileAccess.asBrowserUri('vs/base/test/node/network.test');
|
||||
assert.strictEqual(browserUri.scheme, Schemas.vscodeFileResource);
|
||||
|
||||
const fileUri = FileAccess.asFileUri('vs/base/test/node/network.test', require);
|
||||
const fileUri = FileAccess.asFileUri('vs/base/test/node/network.test');
|
||||
assert.strictEqual(fileUri.scheme, Schemas.file);
|
||||
});
|
||||
|
||||
(isWeb ? test.skip : test)('FileAccess: query and fragment is dropped (native)', () => {
|
||||
const originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' });
|
||||
const browserUri = FileAccess.asBrowserUri(originalFileUri);
|
||||
const browserUri = FileAccess.uriToBrowserUri(originalFileUri);
|
||||
assert.strictEqual(browserUri.query, '');
|
||||
assert.strictEqual(browserUri.fragment, '');
|
||||
});
|
||||
|
||||
(isWeb ? test.skip : test)('FileAccess: query and fragment is kept if URI is already of same scheme (native)', () => {
|
||||
const originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' });
|
||||
const browserUri = FileAccess.asBrowserUri(originalFileUri.with({ scheme: Schemas.vscodeFileResource }));
|
||||
const browserUri = FileAccess.uriToBrowserUri(originalFileUri.with({ scheme: Schemas.vscodeFileResource }));
|
||||
assert.strictEqual(browserUri.query, 'foo=bar');
|
||||
assert.strictEqual(browserUri.fragment, 'something');
|
||||
|
||||
const fileUri = FileAccess.asFileUri(originalFileUri);
|
||||
const fileUri = FileAccess.uriToFileUri(originalFileUri);
|
||||
assert.strictEqual(fileUri.query, 'foo=bar');
|
||||
assert.strictEqual(fileUri.fragment, 'something');
|
||||
});
|
||||
|
||||
(isWeb ? test.skip : test)('FileAccess: web', () => {
|
||||
const originalHttpsUri = URI.file('network.test.ts').with({ scheme: 'https' });
|
||||
const browserUri = FileAccess.asBrowserUri(originalHttpsUri);
|
||||
const browserUri = FileAccess.uriToBrowserUri(originalHttpsUri);
|
||||
assert.strictEqual(originalHttpsUri.toString(), browserUri.toString());
|
||||
});
|
||||
|
||||
test('FileAccess: remote URIs', () => {
|
||||
const originalRemoteUri = URI.file('network.test.ts').with({ scheme: Schemas.vscodeRemote });
|
||||
const browserUri = FileAccess.asBrowserUri(originalRemoteUri);
|
||||
const browserUri = FileAccess.uriToBrowserUri(originalRemoteUri);
|
||||
assert.notStrictEqual(originalRemoteUri.scheme, browserUri.scheme);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -273,7 +273,7 @@ class CodeMain {
|
|||
} catch (error) {
|
||||
|
||||
// Handle unexpected errors (the only expected error is EADDRINUSE that
|
||||
// indicates a second instance of Code is running)
|
||||
// indicates another instance of VS Code is running)
|
||||
if (error.code !== 'EADDRINUSE') {
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
|
@ -293,7 +293,7 @@ class CodeMain {
|
|||
if (!retry || isWindows || error.code !== 'ECONNREFUSED') {
|
||||
if (error.code === 'EPERM') {
|
||||
this.showStartupWarningDialog(
|
||||
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", productService.nameShort),
|
||||
localize('secondInstanceAdmin', "Another instance of {0} is already running as administrator.", productService.nameShort),
|
||||
localize('secondInstanceAdminDetail', "Please close the other instance and try again."),
|
||||
productService.nameLong
|
||||
);
|
||||
|
@ -318,7 +318,7 @@ class CodeMain {
|
|||
|
||||
// Tests from CLI require to be the only instance currently
|
||||
if (environmentMainService.extensionTestsLocationURI && !environmentMainService.debugExtensionHost.break) {
|
||||
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
|
||||
const msg = `Running extension tests from the command line is currently only supported if no other instance of ${productService.nameShort} is running.`;
|
||||
logService.error(msg);
|
||||
client.dispose();
|
||||
|
||||
|
@ -377,7 +377,7 @@ class CodeMain {
|
|||
|
||||
// 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.');
|
||||
logService.warn(`Warning: The --status argument can only be used if ${productService.nameShort} is already running. Please run it again after {0} has started.`);
|
||||
|
||||
throw new ExpectedError('Terminating...');
|
||||
}
|
||||
|
@ -468,7 +468,7 @@ class CodeMain {
|
|||
// is closed and then exit the waiting process.
|
||||
//
|
||||
// Note: we are not doing this if the wait marker has been already
|
||||
// added as argument. This can happen if Code was started from CLI.
|
||||
// added as argument. This can happen if VS Code was started from CLI.
|
||||
|
||||
if (args.wait && !args.waitMarkerFilePath) {
|
||||
const waitMarkerFilePath = createWaitMarkerFileSync(args.verbose);
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'vs/css!./media/issueReporter';
|
|||
import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded
|
||||
import { localize } from 'vs/nls';
|
||||
import { $, reset, safeInnerHtml, windowOpenNoOpener } from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Button, defaultOptions } from 'vs/base/browser/ui/button/button';
|
||||
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
@ -88,7 +88,7 @@ export class IssueReporter extends Disposable {
|
|||
|
||||
const issueReporterElement = this.getElementById('issue-reporter');
|
||||
if (issueReporterElement) {
|
||||
this.previewButton = new Button(issueReporterElement);
|
||||
this.previewButton = new Button(issueReporterElement, defaultOptions);
|
||||
this.updatePreviewButtonState();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,13 +23,12 @@
|
|||
'vs/nls!vs/workbench/workbench.desktop.main',
|
||||
'vs/css!vs/workbench/workbench.desktop.main'
|
||||
],
|
||||
function (_, configuration) {
|
||||
function (desktopMain, configuration) {
|
||||
|
||||
// Mark start of workbench
|
||||
performance.mark('code/didLoadWorkbenchMain');
|
||||
|
||||
// @ts-ignore
|
||||
return require('vs/workbench/electron-sandbox/desktop.main').main(configuration);
|
||||
return desktopMain.main(configuration);
|
||||
},
|
||||
{
|
||||
configureDeveloperSettings: function (windowConfig) {
|
||||
|
|
|
@ -496,7 +496,7 @@ export async function main(argv: string[]): Promise<any> {
|
|||
}
|
||||
|
||||
function getAppRoot() {
|
||||
return dirname(FileAccess.asFileUri('', require).fsPath);
|
||||
return dirname(FileAccess.asFileUri('').fsPath);
|
||||
}
|
||||
|
||||
function eventuallyExit(code: number): void {
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DataTransfers } from 'vs/base/browser/dnd';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { createFileDataTransferItem, createStringDataTransferItem, IDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { createFileDataTransferItem, createStringDataTransferItem, IDataTransferItem, UriList, VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { Mimes } from 'vs/base/common/mime';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CodeDataTransfers, extractEditorsDropData, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd';
|
||||
|
@ -64,13 +63,3 @@ export function addExternalEditorsDropData(dataTransfer: VSDataTransfer, dragEve
|
|||
dataTransfer.delete(internal);
|
||||
}
|
||||
}
|
||||
|
||||
export const UriList = Object.freeze({
|
||||
// http://amundsen.com/hypermedia/urilist/
|
||||
create: (entries: ReadonlyArray<string | URI>): string => {
|
||||
return distinct(entries.map(x => x.toString())).join('\r\n');
|
||||
},
|
||||
parse: (str: string): string[] => {
|
||||
return str.split('\r\n').filter(value => !value.startsWith('#'));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
.monaco-editor .view-ruler {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
box-shadow: 1px 0 0 0 var(--vscode-editorRuler-ruler) inset;
|
||||
}
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
import 'vs/css!./rulers';
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { ViewPart } from 'vs/editor/browser/view/viewPart';
|
||||
import { editorRuler } from 'vs/editor/common/core/editorColorRegistry';
|
||||
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext';
|
||||
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
|
||||
import * as viewEvents from 'vs/editor/common/viewEvents';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorOption, IRulerOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export class Rulers extends ViewPart {
|
||||
|
@ -100,10 +98,3 @@ export class Rulers extends ViewPart {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const rulerColor = theme.getColor(editorRuler);
|
||||
if (rulerColor) {
|
||||
collector.addRule(`.monaco-editor .view-ruler { box-shadow: 1px 0 0 0 ${rulerColor} inset; }`);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
top: 0;
|
||||
left: 0;
|
||||
height: 6px;
|
||||
}
|
||||
box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset;
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ import { ViewPart } from 'vs/editor/browser/view/viewPart';
|
|||
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext';
|
||||
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
|
||||
import * as viewEvents from 'vs/editor/common/viewEvents';
|
||||
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
|
||||
|
@ -91,10 +89,3 @@ export class ScrollDecorationViewPart extends ViewPart {
|
|||
this._domNode.setClassName(this._shouldShow ? 'scroll-decoration' : '');
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const shadow = theme.getColor(scrollbarShadow);
|
||||
if (shadow) {
|
||||
collector.addRule(`.monaco-editor .scroll-decoration { box-shadow: ${shadow} 0 6px 6px -6px inset; }`);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,13 +22,11 @@ import { LineTokens } from 'vs/editor/common/tokens/lineTokens';
|
|||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model';
|
||||
import { editorLineNumbers } from 'vs/editor/common/core/editorColorRegistry';
|
||||
import { RenderLineInput, renderViewLine2 as renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer';
|
||||
import { ViewLineRenderingData } from 'vs/editor/common/viewModel';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
|
@ -817,18 +815,6 @@ export class DiffReview extends Disposable {
|
|||
|
||||
// theming
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const lineNumbers = theme.getColor(editorLineNumbers);
|
||||
if (lineNumbers) {
|
||||
collector.addRule(`.monaco-diff-editor .diff-review-line-number { color: ${lineNumbers}; }`);
|
||||
}
|
||||
|
||||
const shadow = theme.getColor(scrollbarShadow);
|
||||
if (shadow) {
|
||||
collector.addRule(`.monaco-diff-editor .diff-review-shadow { box-shadow: ${shadow} 0 -6px 6px -6px inset; }`);
|
||||
}
|
||||
});
|
||||
|
||||
class DiffReviewNext extends EditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
.monaco-diff-editor .diff-review-line-number {
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
color: var(--vscode-editorLineNumber-foreground);
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review {
|
||||
|
@ -20,6 +21,7 @@
|
|||
|
||||
.monaco-diff-editor .diff-review-shadow {
|
||||
position: absolute;
|
||||
box-shadow: var(--vscode-scrollbar-shadow) 0 -6px 6px -6px inset;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-row {
|
||||
|
|
|
@ -5,4 +5,6 @@
|
|||
|
||||
.monaco-editor .bracket-match {
|
||||
box-sizing: border-box;
|
||||
background-color: var(--vscode-editorBracketMatch-background);
|
||||
border: 1px solid var(--vscode-editorBracketMatch-border);
|
||||
}
|
||||
|
|
|
@ -17,12 +17,11 @@ import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/com
|
|||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { editorBracketMatchBackground, editorBracketMatchBorder } from 'vs/editor/common/core/editorColorRegistry';
|
||||
import * as nls from 'vs/nls';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', { dark: '#A0A0A0', light: '#A0A0A0', hcDark: '#A0A0A0', hcLight: '#A0A0A0' }, nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.'));
|
||||
|
||||
|
@ -360,16 +359,6 @@ export class BracketMatchingController extends Disposable implements IEditorCont
|
|||
registerEditorContribution(BracketMatchingController.ID, BracketMatchingController);
|
||||
registerEditorAction(SelectToBracketAction);
|
||||
registerEditorAction(JumpToBracketAction);
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const bracketMatchBackground = theme.getColor(editorBracketMatchBackground);
|
||||
if (bracketMatchBackground) {
|
||||
collector.addRule(`.monaco-editor .bracket-match { background-color: ${bracketMatchBackground}; }`);
|
||||
}
|
||||
const bracketMatchBorder = theme.getColor(editorBracketMatchBorder);
|
||||
if (bracketMatchBorder) {
|
||||
collector.addRule(`.monaco-editor .bracket-match { border: 1px solid ${bracketMatchBorder}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
// Go to menu
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
|
|
|
@ -29,6 +29,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers';
|
|||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
|
||||
import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types';
|
||||
import { IdleValue } from 'vs/base/common/async';
|
||||
|
||||
function contextKeyForSupportedActions(kind: CodeActionKind) {
|
||||
return ContextKeyExpr.regex(
|
||||
|
@ -92,7 +93,7 @@ export class CodeActionController extends Disposable implements IEditorContribut
|
|||
}
|
||||
|
||||
private readonly _editor: ICodeEditor;
|
||||
private readonly _model: CodeActionModel;
|
||||
private readonly _model: IdleValue<CodeActionModel>;
|
||||
private readonly _ui: Lazy<CodeActionUi>;
|
||||
|
||||
constructor(
|
||||
|
@ -106,8 +107,14 @@ export class CodeActionController extends Disposable implements IEditorContribut
|
|||
super();
|
||||
|
||||
this._editor = editor;
|
||||
this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService));
|
||||
this._register(this._model.onDidChangeState(newState => this.update(newState)));
|
||||
|
||||
this._model = this._register(new IdleValue(() => {
|
||||
const model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService));
|
||||
|
||||
this._register(model.onDidChangeState(newState => this.update(newState)));
|
||||
|
||||
return model;
|
||||
}));
|
||||
|
||||
this._ui = new Lazy(() =>
|
||||
this._register(_instantiationService.createInstance(CodeActionUi, editor, QuickFixAction.Id, AutoFixAction.Id, {
|
||||
|
@ -149,7 +156,7 @@ export class CodeActionController extends Disposable implements IEditorContribut
|
|||
}
|
||||
|
||||
private _trigger(trigger: CodeActionTrigger) {
|
||||
return this._model.trigger(trigger);
|
||||
return this._model.value.trigger(trigger);
|
||||
}
|
||||
|
||||
private _applyCodeAction(action: CodeActionItem, preview: boolean): Promise<void> {
|
||||
|
|
|
@ -7,11 +7,12 @@ import { DataTransfers } from 'vs/base/browser/dnd';
|
|||
import { addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { createStringDataTransferItem, UriList, VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Mimes } from 'vs/base/common/mime';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { toVSDataTransfer, UriList } from 'vs/editor/browser/dnd';
|
||||
import { toVSDataTransfer } from 'vs/editor/browser/dnd';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
@ -69,9 +70,12 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
}
|
||||
|
||||
private arePasteActionsEnabled(model: ITextModel): boolean {
|
||||
return this._configurationService.getValue('editor.experimental.pasteActions.enabled', {
|
||||
resource: model.uri
|
||||
});
|
||||
if (this._configurationService.getValue('editor.experimental.pasteActions.enabled', { resource: model.uri })) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: This check is only here to support enabling `ipynb.pasteImagesAsAttachments.enabled` by default
|
||||
return model.uri.scheme === Schemas.vscodeNotebookCell;
|
||||
}
|
||||
|
||||
private handleCopy(e: ClipboardEvent) {
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
|
||||
import { raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { UriList, VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Mimes } from 'vs/base/common/mime';
|
||||
import { relativePath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { addExternalEditorsDropData, toVSDataTransfer, UriList } from 'vs/editor/browser/dnd';
|
||||
import { addExternalEditorsDropData, toVSDataTransfer } from 'vs/editor/browser/dnd';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
.monaco-editor .inline-folded:after {
|
||||
color: grey;
|
||||
margin: 0.1em 0.2em 0 0.2em;
|
||||
content: "⋯";
|
||||
content: "\22EF"; /* ellipses unicode character */
|
||||
display: inline;
|
||||
line-height: 1em;
|
||||
cursor: pointer;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IdleValue } from 'vs/base/common/async';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
@ -29,7 +30,7 @@ class ParameterHintsController extends Disposable implements IEditorContribution
|
|||
}
|
||||
|
||||
private readonly editor: ICodeEditor;
|
||||
private readonly model: ParameterHintsModel;
|
||||
private readonly model: IdleValue<ParameterHintsModel>;
|
||||
private readonly widget: Lazy<ParameterHintsWidget>;
|
||||
|
||||
constructor(
|
||||
|
@ -41,22 +42,26 @@ class ParameterHintsController extends Disposable implements IEditorContribution
|
|||
|
||||
this.editor = editor;
|
||||
|
||||
this.model = this._register(new ParameterHintsModel(editor, languageFeaturesService.signatureHelpProvider));
|
||||
this.model = this._register(new IdleValue(() => {
|
||||
const model = this._register(new ParameterHintsModel(editor, languageFeaturesService.signatureHelpProvider));
|
||||
|
||||
this._register(this.model.onChangedHints(newParameterHints => {
|
||||
if (newParameterHints) {
|
||||
this.widget.getValue().show();
|
||||
this.widget.getValue().render(newParameterHints);
|
||||
} else {
|
||||
this.widget.rawValue?.hide();
|
||||
}
|
||||
this._register(model.onChangedHints(newParameterHints => {
|
||||
if (newParameterHints) {
|
||||
this.widget.getValue().show();
|
||||
this.widget.getValue().render(newParameterHints);
|
||||
} else {
|
||||
this.widget.rawValue?.hide();
|
||||
}
|
||||
}));
|
||||
|
||||
return model;
|
||||
}));
|
||||
|
||||
this.widget = new Lazy(() => this._register(instantiationService.createInstance(ParameterHintsWidget, this.editor, this.model)));
|
||||
this.widget = new Lazy(() => this._register(instantiationService.createInstance(ParameterHintsWidget, this.editor, this.model.value)));
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.model.cancel();
|
||||
this.model.value.cancel();
|
||||
}
|
||||
|
||||
previous(): void {
|
||||
|
@ -68,7 +73,7 @@ class ParameterHintsController extends Disposable implements IEditorContribution
|
|||
}
|
||||
|
||||
trigger(context: TriggerContext): void {
|
||||
this.model.trigger(context, 0);
|
||||
this.model.value.trigger(context, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,4 +7,8 @@
|
|||
padding: 10px;
|
||||
vertical-align: middle;
|
||||
overflow: scroll;
|
||||
}
|
||||
color: var(--vscode-editorWidget-foreground);
|
||||
background-color: var(--vscode-editorWidget-background);
|
||||
box-shadow: 0 2px 8px var(--vscode-widget-shadow);
|
||||
border: 2px solid var(--vscode-contrastBorder);
|
||||
}
|
||||
|
|
|
@ -26,8 +26,6 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati
|
|||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { contrastBorder, editorWidgetBackground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
@ -362,25 +360,3 @@ registerEditorCommand(
|
|||
}
|
||||
})
|
||||
);
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const widgetBackground = theme.getColor(editorWidgetBackground);
|
||||
if (widgetBackground) {
|
||||
collector.addRule(`.monaco-editor .accessibilityHelpWidget { background-color: ${widgetBackground}; }`);
|
||||
}
|
||||
const widgetForeground = theme.getColor(editorWidgetForeground);
|
||||
if (widgetForeground) {
|
||||
collector.addRule(`.monaco-editor .accessibilityHelpWidget { color: ${widgetForeground}; }`);
|
||||
}
|
||||
|
||||
|
||||
const widgetShadowColor = theme.getColor(widgetShadow);
|
||||
if (widgetShadowColor) {
|
||||
collector.addRule(`.monaco-editor .accessibilityHelpWidget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
|
||||
}
|
||||
|
||||
const hcBorder = theme.getColor(contrastBorder);
|
||||
if (hcBorder) {
|
||||
collector.addRule(`.monaco-editor .accessibilityHelpWidget { border: 2px solid ${hcBorder}; }`);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
|
|||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
@memoize
|
||||
get appRoot(): string { return dirname(FileAccess.asFileUri('', require).fsPath); }
|
||||
get appRoot(): string { return dirname(FileAccess.asFileUri('').fsPath); }
|
||||
|
||||
@memoize
|
||||
get userHome(): URI { return URI.file(this.paths.homeDir); }
|
||||
|
@ -129,7 +129,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
|
|||
return resolve(cliBuiltinExtensionsDir);
|
||||
}
|
||||
|
||||
return normalize(join(FileAccess.asFileUri('', require).fsPath, '..', 'extensions'));
|
||||
return normalize(join(FileAccess.asFileUri('').fsPath, '..', 'extensions'));
|
||||
}
|
||||
|
||||
get extensionsDownloadLocation(): URI {
|
||||
|
|
|
@ -77,7 +77,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
|||
'waitMarkerFilePath': { type: 'string' },
|
||||
'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") },
|
||||
'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") },
|
||||
'profile': { type: 'string', 'cat': 'o', args: 'settingsProfileName', description: localize('settingsProfileName', "Opens the provided folder or workspace with the given profile and associates the profile with the workspace. If the profile does not exist, a new empty one is created. A folder or workspace must be provided for the profile to take effect.") },
|
||||
'profile': { type: 'string', 'cat': 'o', args: 'profileName', description: localize('profileName', "Opens the provided folder or workspace with the given profile and associates the profile with the workspace. If the profile does not exist, a new empty one is created. A folder or workspace must be provided for the profile to take effect.") },
|
||||
'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
|
||||
|
||||
'extensions-dir': { type: 'string', deprecates: ['extensionHomePath'], cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
||||
|
|
|
@ -127,7 +127,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
const installExtensionTaskOptions: InstallExtensionTaskOptions = {
|
||||
...options,
|
||||
installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */
|
||||
profileLocation: !options.profileLocation || isApplicationScopedExtension(manifest) ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation
|
||||
profileLocation: isApplicationScopedExtension(manifest) ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation()
|
||||
};
|
||||
const getInstallExtensionTaskKey = (extension: IGalleryExtension) => `${ExtensionKey.create(extension).toString()}${installExtensionTaskOptions.profileLocation ? `-${installExtensionTaskOptions.profileLocation.toString()}` : ''}`;
|
||||
|
||||
|
@ -467,7 +467,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
private async uninstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise<void> {
|
||||
const uninstallOptions: UninstallExtensionTaskOptions = {
|
||||
...options,
|
||||
profileLocation: !options.profileLocation || extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation
|
||||
profileLocation: extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation()
|
||||
};
|
||||
const getUninstallExtensionTaskKey = (identifier: IExtensionIdentifier) => `${identifier.id.toLowerCase()}${uninstallOptions.versionOnly ? `-${extension.manifest.version}` : ''}${uninstallOptions.profileLocation ? `@${uninstallOptions.profileLocation.toString()}` : ''}`;
|
||||
const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension.identifier));
|
||||
|
@ -659,6 +659,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
|
||||
abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
|
||||
|
||||
protected abstract getCurrentExtensionsManifestLocation(): URI;
|
||||
protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask;
|
||||
protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue