Merge branch 'main' into joh/rewrite-privates

This commit is contained in:
Johannes 2022-11-14 11:56:16 +01:00
commit 58e979f39e
No known key found for this signature in database
GPG key ID: 6DEF802A22264FCA
233 changed files with 2868 additions and 1485 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

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

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

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

View file

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

View file

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

View file

@ -409,7 +409,7 @@
// "activityBar.foreground": "",
// "activityBarBadge.background": "",
// "activityBarBadge.foreground": "",
"activityBarItem.settingsProfilesBackground": "#082877",
"activityBarItem.profilesBackground": "#082877",
// Workbench: Panel
// "panel.background": "",

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.74.0",
"distro": "afbf11bcff4e2c6daeaf653c501d727b6970d50c",
"distro": "f9046f24c03eab03550535a58f6d5be90106b45f",
"author": {
"name": "Microsoft Corporation"
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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[] = [];

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,4 +6,5 @@
.monaco-editor .view-ruler {
position: absolute;
top: 0;
}
box-shadow: 1px 0 0 0 var(--vscode-editorRuler-ruler) inset;
}

View file

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

View file

@ -8,4 +8,5 @@
top: 0;
left: 0;
height: 6px;
}
box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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