mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-08 07:44:33 +01:00
Merge branch 'main' into joh/eslint-jsdoc
This commit is contained in:
+1
-1
@@ -177,7 +177,7 @@
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$REPOS $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed"
|
||||
"value": "$REPOS $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed -label:verified"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
disturl "https://electronjs.org/headers"
|
||||
target "25.5.0"
|
||||
ms_build_id "23084831"
|
||||
target "25.8.0"
|
||||
ms_build_id "23503258"
|
||||
runtime "electron"
|
||||
build_from_source "true"
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
c3fb8cb4804143eb25fce55a179a6f2df8c215ed709104ec235c96e357b20f42 *electron-v25.5.0-darwin-arm64-symbols.zip
|
||||
e6d2a09348d4fe7c9fbd92bf796489a95e625642f0f1ce96169212554cfa6841 *electron-v25.5.0-darwin-arm64.zip
|
||||
15c28e613dfee0f7e46a296bb4aed64e17f9644d7ef19129aeb6480b8da230ab *electron-v25.5.0-darwin-x64-symbols.zip
|
||||
a5c5c0b621daf8242258c89edb2387fbdf1c69125c984f8564d89b87927373e3 *electron-v25.5.0-darwin-x64.zip
|
||||
69d60a69b7f692b9069cdf9e518bcbaca9cec561f40199cc87196929936a34ce *electron-v25.5.0-linux-arm64-symbols.zip
|
||||
adec2ba09faf5f8d8af8997c8bed43c7712eba5546db1e7d06f8357bf4613921 *electron-v25.5.0-linux-arm64.zip
|
||||
0fe7a5d152c9c401671e02ebcd9da34ab9bb0c28598ede3657077a79f8b3e70a *electron-v25.5.0-linux-armv7l-symbols.zip
|
||||
eccb66e4a308a0bd2d90474894370e3d687e32f78672e6b5077c1822c7bc526b *electron-v25.5.0-linux-armv7l.zip
|
||||
82bd9bc9e66f8ae802fe48a51b8d7e2fb599403e9715fa4b859190200a7376b1 *electron-v25.5.0-linux-x64-symbols.zip
|
||||
485cbeb206fccfb4ed42f694100eebb80c9db6639b3537a95823c4fcb7f210cd *electron-v25.5.0-linux-x64.zip
|
||||
ed2c2a7da571b53bcb336b9a2a024753a272df82ece45df83df888df51bf1912 *electron-v25.5.0-win32-arm64-pdb.zip
|
||||
c316f6364e9b4cd61e19d4763c96abda7247a2c31ae7d30e81219c9a7754f11a *electron-v25.5.0-win32-arm64-symbols.zip
|
||||
582ccbfc5a85a093f5639ee476bb5fef18c2d25dab4a60e5f5cb47e64c99f7fb *electron-v25.5.0-win32-arm64.zip
|
||||
5776e650c23e3847b0c52d850f61c57a84a4fa30943c3cc82197250112911b5b *electron-v25.5.0-win32-ia32-pdb.zip
|
||||
730b429ad2c4cae0fe3ba9600a6ee86c79dada77976bac1c75ca2404993495bc *electron-v25.5.0-win32-ia32-symbols.zip
|
||||
7e6e68aa33a89c0d647575b06daf415a2401feb170ebb9cd795e221af321f751 *electron-v25.5.0-win32-ia32.zip
|
||||
0792665fda9255b340a829cdd24601887a2ca8f04cc49f9a6d4557db0c0cd2f2 *electron-v25.5.0-win32-x64-pdb.zip
|
||||
dc2459546951f8418e866857e9111dd83a0789d401906b2200c2f8dd59d9146b *electron-v25.5.0-win32-x64-symbols.zip
|
||||
9bf7980fbc024ba77ea8ab3e2d32088a5f69bf32506a7d2db72ede17028abdf4 *electron-v25.5.0-win32-x64.zip
|
||||
5b1ea601b737842eacf88d7456c4d14e697822753e9a08ede889cce9e20bafb2 *ffmpeg-v25.5.0-darwin-arm64.zip
|
||||
37bf5c75edefc0b6735b44d0b5b06fdd427179a8501f6e93df9840283cdb4a95 *ffmpeg-v25.5.0-darwin-x64.zip
|
||||
bd52d57ff97fb56ac01a3482af905d04f0d4e9c13c53858c6d9f99957eca82da *ffmpeg-v25.5.0-linux-arm64.zip
|
||||
9b3d09177fa1e63e2a6beecfa70aeec30aeb5c1873ff21128a68051c4e23f95d *ffmpeg-v25.5.0-linux-armv7l.zip
|
||||
edc7b1c9f1a0733f109a2c0375a4e40c5bfe0bf28b7f06dcc76e1ada0aa2f125 *ffmpeg-v25.5.0-linux-x64.zip
|
||||
2e28767b3570ea247869a20988cddd23af710eb994d6099404f123390cedeba6 *ffmpeg-v25.5.0-win32-arm64.zip
|
||||
715568eefd7267573a30186ade3de901587baeb1f013200d8ae50b35941b613f *ffmpeg-v25.5.0-win32-ia32.zip
|
||||
29876504452aaa505f696642178968e24b8dc8cc4b055071e6f0f3f073088acc *ffmpeg-v25.5.0-win32-x64.zip
|
||||
88cafda8394985e59d3d84cb4a6692ad04d8e32db9ecd6429e748e41526ddad7 *electron-v25.8.0-darwin-arm64-symbols.zip
|
||||
6e33d3b8041561722ed41777e055a8c15d3f4e61b67367b2618918bcf0cfea76 *electron-v25.8.0-darwin-arm64.zip
|
||||
438ac9915e062a239fb6d2595323c4783d2c820efc9cbcf3d2c1253d0e057e83 *electron-v25.8.0-darwin-x64-symbols.zip
|
||||
798907d2a66bc79202c8213c61e7fd147ae2a8c31c485d814950b11d43bbbba8 *electron-v25.8.0-darwin-x64.zip
|
||||
3243f3764319cff6c942d9f90a86323c36ec05ec51ef01e782c4e9a7194187e1 *electron-v25.8.0-linux-arm64-symbols.zip
|
||||
f24f858b76bf8a2e18419f62e0f891712b2fa541089123e9caa8d5cd67fc3276 *electron-v25.8.0-linux-arm64.zip
|
||||
dc3ff0489a0ebeda56d06b31eeae75dd7321a52bb601069c4475c56462b4814a *electron-v25.8.0-linux-armv7l-symbols.zip
|
||||
3b7a0c3899f828a5cf30043b73992e90231400b90c1afa700a44f892a55e326b *electron-v25.8.0-linux-armv7l.zip
|
||||
44803b2487406eca8fff9cec405e9e50bd92a911808dfaaa523b9ef52a0e72d8 *electron-v25.8.0-linux-x64-symbols.zip
|
||||
d54fb2df0ad7318240220aa26327171ed1e891fb296f3c27c58b8b487c4df8eb *electron-v25.8.0-linux-x64.zip
|
||||
bf7be6c0c8d0df06f0ce22e16c97aea823415d7f5cbf0ffdadf65d75feaf3cd8 *electron-v25.8.0-win32-arm64-pdb.zip
|
||||
5d91757660b44bf30907f9c2b52225ade4d127d0fe48dc83dec134cc06c949f0 *electron-v25.8.0-win32-arm64-symbols.zip
|
||||
d1e6f30a8d8c7aed28d08ddf915d79de6b16b3a0a7c84c45fd3cc0d47f2b7f53 *electron-v25.8.0-win32-arm64.zip
|
||||
e389fef61c14ea0eefad91a9725aa0afd4dbdc982f7b30aba97bd9c2871c2061 *electron-v25.8.0-win32-ia32-pdb.zip
|
||||
374d6c8897f97fab04e990ecf928e05f643ae33801546bf7d39bf4045b9d8b52 *electron-v25.8.0-win32-ia32-symbols.zip
|
||||
73fc3382202b70dcaf7928f09a791662de82c701b8f403ed72cc5aa9b1401593 *electron-v25.8.0-win32-ia32.zip
|
||||
010d248bd2e77585e1fa977e58b016659566de5a91c1e6845c85a7e6e1851bb9 *electron-v25.8.0-win32-x64-pdb.zip
|
||||
72adb74fd92edff35c177c3c5d96765f230bc7adb8af11b30d5122b9e54c26e1 *electron-v25.8.0-win32-x64-symbols.zip
|
||||
0051d0f241aedc6cdab4751c60f48758936122796f06c9e3033c7710a531686c *electron-v25.8.0-win32-x64.zip
|
||||
2956915642c45eb0099228368d0af50e891e4c10014fa4d3d3bcfb135fbb89a7 *ffmpeg-v25.8.0-darwin-arm64.zip
|
||||
099ee69d44f8ac3802cdd612895f279f7adb043a5b9c9d123479b0f96514a44c *ffmpeg-v25.8.0-darwin-x64.zip
|
||||
bd52d57ff97fb56ac01a3482af905d04f0d4e9c13c53858c6d9f99957eca82da *ffmpeg-v25.8.0-linux-arm64.zip
|
||||
9b3d09177fa1e63e2a6beecfa70aeec30aeb5c1873ff21128a68051c4e23f95d *ffmpeg-v25.8.0-linux-armv7l.zip
|
||||
edc7b1c9f1a0733f109a2c0375a4e40c5bfe0bf28b7f06dcc76e1ada0aa2f125 *ffmpeg-v25.8.0-linux-x64.zip
|
||||
a58e9480dab981ff973749e9d1e08936b2dd63a4b7f9523c030b1833387a4eb5 *ffmpeg-v25.8.0-win32-arm64.zip
|
||||
6866b23a4d561c0322aeb7690aae646718c54398739946e352bf80d0dd721bfd *ffmpeg-v25.8.0-win32-ia32.zip
|
||||
7b906df4ad6252881cf1e58619285b624f74d593379fbc6728e238b852d6abad *ffmpeg-v25.8.0-win32-x64.zip
|
||||
|
||||
+2
-2
@@ -528,12 +528,12 @@
|
||||
"git": {
|
||||
"name": "electron",
|
||||
"repositoryUrl": "https://github.com/electron/electron",
|
||||
"commitHash": "34be316c404e84cdd967fa0e10fceeb6424eed90"
|
||||
"commitHash": "84d7f7f071ae11637d4a41b95536410293672750"
|
||||
}
|
||||
},
|
||||
"isOnlyProductionDependency": true,
|
||||
"license": "MIT",
|
||||
"version": "25.5.0"
|
||||
"version": "25.8.0"
|
||||
},
|
||||
{
|
||||
"component": {
|
||||
|
||||
@@ -52,6 +52,7 @@ const VERSION: &str = concatcp!(NUMBER_IN_VERSION, " (commit ", COMMIT_IN_VERSIO
|
||||
#[clap(
|
||||
help_template = INTEGRATED_TEMPLATE,
|
||||
long_about = None,
|
||||
name = constants::APPLICATION_NAME,
|
||||
version = VERSION,
|
||||
)]
|
||||
pub struct IntegratedCli {
|
||||
@@ -84,6 +85,7 @@ pub struct CliCore {
|
||||
help_template = STANDALONE_TEMPLATE,
|
||||
long_about = None,
|
||||
version = VERSION,
|
||||
name = constants::APPLICATION_NAME,
|
||||
)]
|
||||
pub struct StandaloneCli {
|
||||
#[clap(flatten)]
|
||||
@@ -173,6 +175,7 @@ pub enum Commands {
|
||||
Version(VersionArgs),
|
||||
|
||||
/// Runs a local web version of VS Code.
|
||||
#[clap(about = concatcp!("Runs a local web version of ", constants::PRODUCT_NAME_LONG))]
|
||||
ServeWeb(ServeWebArgs),
|
||||
|
||||
/// Runs the control server on process stdin/stdout
|
||||
|
||||
@@ -25,6 +25,7 @@ use crate::download_cache::DownloadCache;
|
||||
use crate::log;
|
||||
use crate::options::Quality;
|
||||
use crate::state::{LauncherPaths, PersistedState};
|
||||
use crate::tunnels::shutdown_signal::ShutdownRequest;
|
||||
use crate::update_service::{
|
||||
unzip_downloaded_release, Platform, Release, TargetKind, UpdateService,
|
||||
};
|
||||
@@ -98,12 +99,20 @@ pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result<i3
|
||||
async move { Ok::<_, Infallible>(service) }
|
||||
};
|
||||
|
||||
let mut shutdown = ShutdownRequest::create_rx([ShutdownRequest::CtrlC]);
|
||||
let r = if let Some(s) = args.socket_path {
|
||||
let socket = listen_socket_rw_stream(&PathBuf::from(&s)).await?;
|
||||
ctx.log.result(format!("Web UI available on {}", s));
|
||||
Server::builder(socket.into_pollable())
|
||||
let s = PathBuf::from(&s);
|
||||
let socket = listen_socket_rw_stream(&s).await?;
|
||||
ctx.log
|
||||
.result(format!("Web UI available on {}", s.display()));
|
||||
let r = Server::builder(socket.into_pollable())
|
||||
.serve(make_service_fn(|_| make_svc()))
|
||||
.await
|
||||
.with_graceful_shutdown(async {
|
||||
let _ = shutdown.wait().await;
|
||||
})
|
||||
.await;
|
||||
let _ = std::fs::remove_file(&s); // cleanup
|
||||
r
|
||||
} else {
|
||||
let addr: SocketAddr = match &args.host {
|
||||
Some(h) => {
|
||||
@@ -120,6 +129,9 @@ pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result<i3
|
||||
|
||||
Server::bind(&addr)
|
||||
.serve(make_service_fn(|_| make_svc()))
|
||||
.with_graceful_shutdown(async {
|
||||
let _ = shutdown.wait().await;
|
||||
})
|
||||
.await
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::tunnels::paths::{get_server_folder_name, SERVER_FOLDER_NAME};
|
||||
use crate::update_service::{
|
||||
unzip_downloaded_release, Platform, Release, TargetKind, UpdateService,
|
||||
};
|
||||
use crate::util::command::{capture_command, kill_tree};
|
||||
use crate::util::command::{capture_command, capture_command_and_check_status, kill_tree};
|
||||
use crate::util::errors::{wrap, AnyError, CodeError, ExtensionInstallFailed, WrappedError};
|
||||
use crate::util::http::{self, BoxedHttp};
|
||||
use crate::util::io::SilentCopyProgress;
|
||||
@@ -416,11 +416,23 @@ impl<'a> ServerBuilder<'a> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
unzip_downloaded_release(
|
||||
&archive_path,
|
||||
&target_dir.join(SERVER_FOLDER_NAME),
|
||||
SilentCopyProgress(),
|
||||
)?;
|
||||
let server_dir = target_dir.join(SERVER_FOLDER_NAME);
|
||||
unzip_downloaded_release(&archive_path, &server_dir, SilentCopyProgress())?;
|
||||
|
||||
let output = capture_command_and_check_status(
|
||||
server_dir
|
||||
.join("bin")
|
||||
.join(self.server_params.release.quality.server_entrypoint()),
|
||||
&["--version"],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| wrap(e, "error checking server integrity"))?;
|
||||
|
||||
trace!(
|
||||
self.logger,
|
||||
"Server integrity verified, version: {}",
|
||||
String::from_utf8_lossy(&output.stdout).replace('\n', " / ")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -212,6 +212,7 @@ impl ActiveTunnel {
|
||||
|
||||
const VSCODE_CLI_TUNNEL_TAG: &str = "vscode-server-launcher";
|
||||
const VSCODE_CLI_FORWARDING_TAG: &str = "vscode-port-forward";
|
||||
const OWNED_TUNNEL_TAGS: &[&str] = &[VSCODE_CLI_TUNNEL_TAG, VSCODE_CLI_FORWARDING_TAG];
|
||||
const MAX_TUNNEL_NAME_LENGTH: usize = 20;
|
||||
|
||||
fn get_host_token_from_tunnel(tunnel: &Tunnel) -> String {
|
||||
@@ -635,7 +636,7 @@ impl DevTunnels {
|
||||
"Tunnel limit hit, trying to recycle an old tunnel"
|
||||
);
|
||||
|
||||
let existing_tunnels = self.list_all_server_tunnels().await?;
|
||||
let existing_tunnels = self.list_tunnels_with_tag(OWNED_TUNNEL_TAGS).await?;
|
||||
|
||||
let recyclable = existing_tunnels
|
||||
.iter()
|
||||
@@ -667,13 +668,15 @@ impl DevTunnels {
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_all_server_tunnels(&mut self) -> Result<Vec<Tunnel>, AnyError> {
|
||||
async fn list_tunnels_with_tag(
|
||||
&mut self,
|
||||
tags: &[&'static str],
|
||||
) -> Result<Vec<Tunnel>, AnyError> {
|
||||
let tunnels = spanf!(
|
||||
self.log,
|
||||
self.log.span("dev-tunnel.listall"),
|
||||
self.client.list_all_tunnels(&TunnelRequestOptions {
|
||||
tags: vec![self.tag.to_string()],
|
||||
require_all_tags: true,
|
||||
tags: tags.iter().map(|t| t.to_string()).collect(),
|
||||
..Default::default()
|
||||
})
|
||||
)
|
||||
@@ -711,7 +714,7 @@ impl DevTunnels {
|
||||
preferred_name: Option<&str>,
|
||||
mut use_random_name: bool,
|
||||
) -> Result<String, AnyError> {
|
||||
let existing_tunnels = self.list_all_server_tunnels().await?;
|
||||
let existing_tunnels = self.list_tunnels_with_tag(&[self.tag]).await?;
|
||||
let is_name_free = |n: &str| {
|
||||
!existing_tunnels.iter().any(|v| {
|
||||
v.status
|
||||
|
||||
@@ -2950,13 +2950,13 @@
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.folder%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == folder && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == folder && scm.providerCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.workspace%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && scm.providerCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
@@ -2992,13 +2992,13 @@
|
||||
{
|
||||
"view": "explorer",
|
||||
"contents": "%view.workbench.cloneRepository%",
|
||||
"when": "config.git.enabled && git.state == initialized && scmRepositoryCount == 0",
|
||||
"when": "config.git.enabled && git.state == initialized && scm.providerCount == 0",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "explorer",
|
||||
"contents": "%view.workbench.learnMore%",
|
||||
"when": "config.git.enabled && git.state == initialized && scmRepositoryCount == 0",
|
||||
"when": "config.git.enabled && git.state == initialized && scm.providerCount == 0",
|
||||
"group": "5_scm@10"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -200,7 +200,9 @@ const allFlows: IFlow[] = [
|
||||
// other flows that work well.
|
||||
supportsGitHubEnterpriseServer: false,
|
||||
supportsHostedGitHubEnterprise: true,
|
||||
supportsRemoteExtensionHost: true,
|
||||
// Opening a port on the remote side can't be open in the browser on
|
||||
// the client side so this flow won't work in remote extension hosts
|
||||
supportsRemoteExtensionHost: false,
|
||||
// Web worker can't open a port to listen for the redirect
|
||||
supportsWebWorkerExtensionHost: false,
|
||||
// exchanging a code for a token requires a client secret
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
"title": "%cleanInvalidImageAttachment.title%"
|
||||
},
|
||||
{
|
||||
"command": "notebook.cellOutput.copyToClipboard",
|
||||
"title": "%copyOutputToClipboard.title%"
|
||||
"command": "notebook.cellOutput.copy",
|
||||
"title": "%copyCellOutput.title%"
|
||||
}
|
||||
],
|
||||
"notebooks": [
|
||||
@@ -106,7 +106,7 @@
|
||||
],
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "notebook.cellOutput.copyToClipboard",
|
||||
"command": "notebook.cellOutput.copy",
|
||||
"when": "webviewId == 'notebook.output' && webviewSection == 'image'"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"newUntitledIpynb.shortTitle": "Jupyter Notebook",
|
||||
"openIpynbInNotebookEditor.title": "Open IPYNB File In Notebook Editor",
|
||||
"cleanInvalidImageAttachment.title": "Clean Invalid Image Attachment Reference",
|
||||
"copyOutputToClipboard.title": "Copy Output to Clipboard",
|
||||
"copyCellOutput.title": "Copy Output",
|
||||
"markdownAttachmentRenderer.displayName": {
|
||||
"message": "Markdown-It ipynb Cell Attachment renderer",
|
||||
"comment": [
|
||||
|
||||
@@ -25,7 +25,7 @@ const cliPath = process.env.VSCODE_FORWARDING_IS_DEV
|
||||
? path.join(__dirname, '../../../cli/target/debug/code')
|
||||
: path.join(
|
||||
vscode.env.appRoot,
|
||||
process.platform === 'win32' ? '../../bin' : 'bin',
|
||||
process.platform === 'darwin' ? 'bin' : '../../bin',
|
||||
vscode.env.appQuality === 'stable' ? 'code-tunnel' : 'code-tunnel-insiders',
|
||||
) + (process.platform === 'win32' ? '.exe' : '');
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ export const splitNewLines = () => new StreamSplitter('\n'.charCodeAt(0));
|
||||
|
||||
/**
|
||||
* Copied and simplified from src\vs\base\node\nodeStreams.ts
|
||||
*
|
||||
* Exception: does not include the split character in the output.
|
||||
*/
|
||||
export class StreamSplitter extends Transform {
|
||||
private buffer: Buffer | undefined;
|
||||
@@ -31,7 +33,7 @@ export class StreamSplitter extends Transform {
|
||||
break;
|
||||
}
|
||||
|
||||
this.push(this.buffer.subarray(offset, index + 1));
|
||||
this.push(this.buffer.subarray(offset, index));
|
||||
offset = index + 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -977,6 +977,22 @@ suite('vscode API - workspace', () => {
|
||||
assert.strictEqual(document.getText(), expected);
|
||||
});
|
||||
|
||||
|
||||
test('[Bug] Failed to create new test file when in an untitled file #1261', async function () {
|
||||
const uri = vscode.Uri.parse('untitled:Untitled-5.test');
|
||||
const contents = `Hello Test File ${uri.toString()}`;
|
||||
const we = new vscode.WorkspaceEdit();
|
||||
we.createFile(uri, { ignoreIfExists: true });
|
||||
we.replace(uri, new vscode.Range(0, 0, 0, 0), contents);
|
||||
|
||||
const success = await vscode.workspace.applyEdit(we);
|
||||
|
||||
assert.ok(success);
|
||||
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
assert.strictEqual(doc.getText(), contents);
|
||||
});
|
||||
|
||||
test('Should send a single FileWillRenameEvent instead of separate events when moving multiple files at once#111867, 1/3', async function () {
|
||||
|
||||
const file1 = await createRandomFile();
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.82.0",
|
||||
"distro": "49cc0fbc0a8e222bcce2a7c3bf62e0d23f20d258",
|
||||
"version": "1.83.0",
|
||||
"distro": "0a5805caff2d59440704a3bf75eebaa509be862f",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -150,7 +150,7 @@
|
||||
"cssnano": "^4.1.11",
|
||||
"debounce": "^1.0.0",
|
||||
"deemon": "^1.8.0",
|
||||
"electron": "25.5.0",
|
||||
"electron": "25.8.0",
|
||||
"eslint": "8.36.0",
|
||||
"eslint-plugin-header": "3.1.1",
|
||||
"eslint-plugin-jsdoc": "^46.5.0",
|
||||
|
||||
+2
-2
@@ -52,8 +52,8 @@
|
||||
},
|
||||
{
|
||||
"name": "ms-vscode.js-debug",
|
||||
"version": "1.81.0",
|
||||
"sha256": "6d1c7ee89881afd65e8fee47445b6a1c5fb345bf30e2bdf70cd2fdd8d1ff6dec",
|
||||
"version": "1.82.0",
|
||||
"sha256": "4fba41b4b764c3f5a6591d6d9a5bdc59b417f2d799071c889c2b54163f256282",
|
||||
"repo": "https://github.com/microsoft/vscode-js-debug",
|
||||
"metadata": {
|
||||
"id": "25629058-ddac-4e17-abba-74678e126c5d",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
disturl "https://nodejs.org/dist"
|
||||
target "18.15.0"
|
||||
ms_build_id "223745"
|
||||
ms_build_id "229541"
|
||||
runtime "node"
|
||||
build_from_source "true"
|
||||
|
||||
Binary file not shown.
@@ -532,13 +532,16 @@ class TypeNavigationController<T> implements IDisposable {
|
||||
const prefix = matchesPrefix(word, labelStr);
|
||||
const fuzzy = matchesFuzzy2(word, labelStr);
|
||||
|
||||
// ensures that when fuzzy matching, it doesn't clash with prefix matching (1 input vs 1+ should be prefix and fuzzy respecitvely)
|
||||
const fuzzyScore = fuzzy ? fuzzy[0].end - fuzzy[0].start : 0;
|
||||
if (prefix || fuzzyScore > 1) {
|
||||
this.previouslyFocused = start;
|
||||
this.list.setFocus([index]);
|
||||
this.list.reveal(index);
|
||||
return;
|
||||
if (fuzzy) {
|
||||
const fuzzyScore = fuzzy[0].end - fuzzy[0].start;
|
||||
|
||||
// ensures that when fuzzy matching, doesn't clash with prefix matching (1 input vs 1+ should be prefix and fuzzy respecitvely). Also makes sure that exact matches are prioritized.
|
||||
if (prefix || (fuzzyScore > 1 && fuzzy.length === 1)) {
|
||||
this.previouslyFocused = start;
|
||||
this.list.setFocus([index]);
|
||||
this.list.reveal(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -729,10 +732,6 @@ export class MouseController<T> implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSelectionRangeChangeEvent(e)) {
|
||||
return this.changeSelection(e);
|
||||
}
|
||||
|
||||
if (this.isSelectionChangeEvent(e)) {
|
||||
return this.changeSelection(e);
|
||||
}
|
||||
|
||||
@@ -666,6 +666,10 @@ export namespace CompareResult {
|
||||
return result < 0;
|
||||
}
|
||||
|
||||
export function isLessThanOrEqual(result: CompareResult): boolean {
|
||||
return result <= 0;
|
||||
}
|
||||
|
||||
export function isGreaterThan(result: CompareResult): boolean {
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
@@ -567,6 +567,7 @@ export const Codicon = {
|
||||
send: register('send', 0xec0f),
|
||||
sparkle: register('sparkle', 0xec10),
|
||||
insert: register('insert', 0xec11),
|
||||
mic: register('mic', 0xec12),
|
||||
|
||||
|
||||
// derived icons, that could become separate icons
|
||||
|
||||
@@ -557,6 +557,24 @@ export namespace Event {
|
||||
return new Promise(resolve => once(event)(resolve));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event out of a promise that fires once when the promise is
|
||||
* resolved with the result of the promise or `undefined`.
|
||||
*/
|
||||
export function fromPromise<T>(promise: Promise<T>): Event<T | undefined> {
|
||||
const result = new Emitter<T | undefined>();
|
||||
|
||||
promise.then(res => {
|
||||
result.fire(res);
|
||||
}, () => {
|
||||
result.fire(undefined);
|
||||
}).finally(() => {
|
||||
result.dispose();
|
||||
});
|
||||
|
||||
return result.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to an event and calls the listener immediately with undefined as the event object.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { stub } from 'sinon';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { DeferredPromise, timeout } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { AsyncEmitter, DebounceEmitter, DynamicListEventMultiplexer, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay, createEventDeliveryQueue } from 'vs/base/common/event';
|
||||
@@ -1158,6 +1158,48 @@ suite('Event utils', () => {
|
||||
listener.dispose(); // should not crash
|
||||
});
|
||||
|
||||
suite('fromPromise', () => {
|
||||
|
||||
test('not yet resolved', async function () {
|
||||
return new Promise(resolve => {
|
||||
let promise = new DeferredPromise<number>();
|
||||
|
||||
Event.fromPromise(promise.p)(e => {
|
||||
assert.strictEqual(e, 1);
|
||||
|
||||
promise = new DeferredPromise();
|
||||
|
||||
Event.fromPromise(promise.p)(() => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
promise.error(undefined);
|
||||
});
|
||||
|
||||
promise.complete(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('already resolved', async function () {
|
||||
return new Promise(resolve => {
|
||||
let promise = new DeferredPromise<number>();
|
||||
promise.complete(1);
|
||||
|
||||
Event.fromPromise(promise.p)(e => {
|
||||
assert.strictEqual(e, 1);
|
||||
|
||||
promise = new DeferredPromise();
|
||||
promise.error(undefined);
|
||||
|
||||
Event.fromPromise(promise.p)(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Relay', () => {
|
||||
test('should input work', () => {
|
||||
const e1 = new Emitter<number>();
|
||||
|
||||
@@ -17,6 +17,7 @@ import product from 'vs/platform/product/common/product';
|
||||
import { ISecretStorageProvider } from 'vs/platform/secrets/common/secrets';
|
||||
import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/window/common/window';
|
||||
import type { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api';
|
||||
import { AuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import type { IWorkspace, IWorkspaceProvider } from 'vs/workbench/services/host/browser/browserHostService';
|
||||
import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
|
||||
import { create } from 'vs/workbench/workbench.web.main';
|
||||
@@ -176,11 +177,13 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider
|
||||
) { }
|
||||
|
||||
private async load(): Promise<Record<string, string>> {
|
||||
const record = this.loadAuthSessionFromElement();
|
||||
// Get the secrets from localStorage
|
||||
const encrypted = window.localStorage.getItem(this._storageKey);
|
||||
if (encrypted) {
|
||||
try {
|
||||
return JSON.parse(await this.crypto.unseal(encrypted));
|
||||
const decrypted = JSON.parse(await this.crypto.unseal(encrypted));
|
||||
return { ...record, ...decrypted };
|
||||
} catch (err) {
|
||||
// TODO: send telemetry
|
||||
console.error('Failed to decrypt secrets from localStorage', err);
|
||||
@@ -188,7 +191,42 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
return record;
|
||||
}
|
||||
|
||||
private loadAuthSessionFromElement(): Record<string, string> {
|
||||
let authSessionInfo: (AuthenticationSessionInfo & { scopes: string[][] }) | undefined;
|
||||
const authSessionElement = document.getElementById('vscode-workbench-auth-session');
|
||||
const authSessionElementAttribute = authSessionElement ? authSessionElement.getAttribute('data-settings') : undefined;
|
||||
if (authSessionElementAttribute) {
|
||||
try {
|
||||
authSessionInfo = JSON.parse(authSessionElementAttribute);
|
||||
} catch (error) { /* Invalid session is passed. Ignore. */ }
|
||||
}
|
||||
|
||||
if (!authSessionInfo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const record: Record<string, string> = {};
|
||||
|
||||
// Settings Sync Entry
|
||||
record[`${product.urlProtocol}.loginAccount`] = JSON.stringify(authSessionInfo);
|
||||
|
||||
// Auth extension Entry
|
||||
if (authSessionInfo.providerId !== 'github') {
|
||||
console.error(`Unexpected auth provider: ${authSessionInfo.providerId}. Expected 'github'.`);
|
||||
return record;
|
||||
}
|
||||
|
||||
const authAccount = JSON.stringify({ extensionId: 'vscode.github-authentication', key: 'github.auth' });
|
||||
record[authAccount] = JSON.stringify(authSessionInfo.scopes.map(scopes => ({
|
||||
id: authSessionInfo!.id,
|
||||
scopes,
|
||||
accessToken: authSessionInfo!.accessToken
|
||||
})));
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async get(key: string): Promise<string | undefined> {
|
||||
|
||||
@@ -41,12 +41,42 @@ export class ElementSizeObserver extends Disposable {
|
||||
|
||||
public startObserving(): void {
|
||||
if (!this._resizeObserver && this._referenceDomElement) {
|
||||
this._resizeObserver = new ResizeObserver((entries) => {
|
||||
if (entries && entries[0] && entries[0].contentRect) {
|
||||
this.observe({ width: entries[0].contentRect.width, height: entries[0].contentRect.height });
|
||||
// We want to react to the resize observer only once per animation frame
|
||||
// The first time the resize observer fires, we will react to it immediately.
|
||||
// Otherwise we will postpone to the next animation frame.
|
||||
// We'll use `observeContentRect` to store the content rect we received.
|
||||
|
||||
let observeContentRect: DOMRectReadOnly | null = null;
|
||||
const observeNow = () => {
|
||||
if (observeContentRect) {
|
||||
this.observe({ width: observeContentRect.width, height: observeContentRect.height });
|
||||
} else {
|
||||
this.observe();
|
||||
}
|
||||
};
|
||||
|
||||
let shouldObserve = false;
|
||||
let alreadyObservedThisAnimationFrame = false;
|
||||
|
||||
const update = () => {
|
||||
if (shouldObserve && !alreadyObservedThisAnimationFrame) {
|
||||
try {
|
||||
shouldObserve = false;
|
||||
alreadyObservedThisAnimationFrame = true;
|
||||
observeNow();
|
||||
} finally {
|
||||
requestAnimationFrame(() => {
|
||||
alreadyObservedThisAnimationFrame = false;
|
||||
update();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this._resizeObserver = new ResizeObserver((entries) => {
|
||||
observeContentRect = (entries && entries[0] && entries[0].contentRect ? entries[0].contentRect : null);
|
||||
shouldObserve = true;
|
||||
update();
|
||||
});
|
||||
this._resizeObserver.observe(this._referenceDomElement);
|
||||
}
|
||||
|
||||
@@ -4,22 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, IReader, ISettableObservable, ITransaction, autorunWithStore, derived, observableSignal, observableSignalFromEvent, observableValue, transaction, waitForState } from 'vs/base/common/observable';
|
||||
import { isDefined } from 'vs/base/common/types';
|
||||
import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
|
||||
import { ISerializedLineRange, LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { AdvancedLinesDiffComputer } from 'vs/editor/common/diff/advancedLinesDiffComputer';
|
||||
import { IDocumentDiff, IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider';
|
||||
import { LineRangeMapping, MovedText, RangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { AdvancedLinesDiffComputer, lineRangeMappingFromRangeMappings } from 'vs/editor/common/diff/advancedLinesDiffComputer';
|
||||
import { LineRangeMapping, MovedText } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { IDiffEditorModel, IDiffEditorViewModel } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper';
|
||||
import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos';
|
||||
import { lengthAdd, lengthDiffNonNegative, lengthGetLineCount, lengthOfRange, lengthToPosition, lengthZero, positionToLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length';
|
||||
import { DiffEditorOptions } from './diffEditorOptions';
|
||||
import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
export class DiffEditorViewModel extends Disposable implements IDiffEditorViewModel {
|
||||
private readonly _isDiffUpToDate = observableValue<boolean>('isDiffUpToDate', false);
|
||||
@@ -469,6 +466,9 @@ export class UnchangedRegion {
|
||||
}
|
||||
|
||||
function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff | undefined {
|
||||
return undefined;
|
||||
/*
|
||||
TODO@hediet
|
||||
if (textEdits.length === 0) {
|
||||
return diff;
|
||||
}
|
||||
@@ -478,9 +478,9 @@ function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
|
||||
if (!diff3) {
|
||||
return undefined;
|
||||
}
|
||||
return flip(diff3);
|
||||
return flip(diff3);*/
|
||||
}
|
||||
|
||||
/*
|
||||
function flip(diff: IDocumentDiff): IDocumentDiff {
|
||||
return {
|
||||
changes: diff.changes.map(c => c.flip()),
|
||||
@@ -489,8 +489,11 @@ function flip(diff: IDocumentDiff): IDocumentDiff {
|
||||
quitEarly: diff.quitEarly,
|
||||
};
|
||||
}
|
||||
|
||||
*/
|
||||
function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff | undefined {
|
||||
return undefined;
|
||||
/*
|
||||
TODO@hediet
|
||||
if (textEdits.length === 0) {
|
||||
return diff;
|
||||
}
|
||||
@@ -514,9 +517,9 @@ function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
|
||||
quitEarly: false,
|
||||
changes,
|
||||
moves,
|
||||
};
|
||||
};*/
|
||||
}
|
||||
|
||||
/*
|
||||
function applyEditToLineRange(range: LineRange, textEdits: TextEditInfo[]): LineRange | undefined {
|
||||
let rangeStartLineNumber = range.startLineNumber;
|
||||
let rangeEndLineNumberEx = range.endLineNumberExclusive;
|
||||
@@ -583,3 +586,4 @@ function applyModifiedEditsToLineRangeMappings(changes: readonly LineRangeMappin
|
||||
);
|
||||
return newChanges;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Action } from 'vs/base/common/actions';
|
||||
import { booleanComparator, compareBy, findMaxIdxBy, numberComparator, tieBreakComparators } from 'vs/base/common/arrays';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, autorun, autorunWithStore, constObservable, derived, derivedWithStore, keepAlive, observableFromEvent, observableSignalFromEvent, observableValue } from 'vs/base/common/observable';
|
||||
import { IObservable, autorun, autorunHandleChanges, autorunWithStore, constObservable, derived, derivedWithStore, keepAlive, observableFromEvent, observableSignalFromEvent, observableValue } from 'vs/base/common/observable';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors';
|
||||
@@ -82,19 +82,50 @@ export class MovedBlocksLinesPart extends Disposable {
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._editors.original.onDidChangeCursorPosition(e => {
|
||||
const m = this._diffModel.get();
|
||||
if (!m) { return; }
|
||||
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.original.contains(e.position.lineNumber));
|
||||
if (movedText !== m.movedTextToCompare.get()) {
|
||||
m.movedTextToCompare.set(undefined, undefined);
|
||||
const originalCursorPosition = observableFromEvent(this._editors.original.onDidChangeCursorPosition, () => this._editors.original.getPosition());
|
||||
const modifiedCursorPosition = observableFromEvent(this._editors.modified.onDidChangeCursorPosition, () => this._editors.modified.getPosition());
|
||||
const originalHasFocus = observableSignalFromEvent(
|
||||
'original.onDidFocusEditorWidget',
|
||||
e => this._editors.original.onDidFocusEditorWidget(() => setTimeout(() => e(undefined), 0))
|
||||
);
|
||||
const modifiedHasFocus = observableSignalFromEvent(
|
||||
'modified.onDidFocusEditorWidget',
|
||||
e => this._editors.modified.onDidFocusEditorWidget(() => setTimeout(() => e(undefined), 0))
|
||||
);
|
||||
|
||||
let lastChangedEditor: 'original' | 'modified' = 'modified';
|
||||
|
||||
this._register(autorunHandleChanges({
|
||||
createEmptyChangeSummary: () => undefined,
|
||||
handleChange: (ctx, summary) => {
|
||||
if (ctx.didChange(originalHasFocus)) { lastChangedEditor = 'original'; }
|
||||
if (ctx.didChange(modifiedHasFocus)) { lastChangedEditor = 'modified'; }
|
||||
return true;
|
||||
}
|
||||
m.setActiveMovedText(movedText);
|
||||
}));
|
||||
this._register(this._editors.modified.onDidChangeCursorPosition(e => {
|
||||
const m = this._diffModel.get();
|
||||
}, reader => {
|
||||
originalHasFocus.read(reader);
|
||||
modifiedHasFocus.read(reader);
|
||||
|
||||
const m = this._diffModel.read(reader);
|
||||
if (!m) { return; }
|
||||
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.modified.contains(e.position.lineNumber));
|
||||
const diff = m.diff.read(reader);
|
||||
|
||||
let movedText: MovedText | undefined = undefined;
|
||||
|
||||
if (diff && lastChangedEditor === 'original') {
|
||||
const originalPos = originalCursorPosition.read(reader);
|
||||
if (originalPos) {
|
||||
movedText = diff.movedTexts.find(m => m.lineRangeMapping.original.contains(originalPos.lineNumber));
|
||||
}
|
||||
}
|
||||
|
||||
if (diff && lastChangedEditor === 'modified') {
|
||||
const modifiedPos = modifiedCursorPosition.read(reader);
|
||||
if (modifiedPos) {
|
||||
movedText = diff.movedTexts.find(m => m.lineRangeMapping.modified.contains(modifiedPos.lineNumber));
|
||||
}
|
||||
}
|
||||
|
||||
if (movedText !== m.movedTextToCompare.get()) {
|
||||
m.movedTextToCompare.set(undefined, undefined);
|
||||
}
|
||||
@@ -324,7 +355,7 @@ class MovedBlockOverlayWidget extends ViewZoneOverlayWidget {
|
||||
true,
|
||||
() => {
|
||||
this._editor.focus();
|
||||
this._diffModel.movedTextToCompare.set(this._diffModel.movedTextToCompare.get() ? undefined : this._move, undefined);
|
||||
this._diffModel.movedTextToCompare.set(this._diffModel.movedTextToCompare.get() === _move ? undefined : this._move, undefined);
|
||||
},
|
||||
);
|
||||
this._register(autorun(reader => {
|
||||
|
||||
@@ -94,7 +94,7 @@ export class UnchangedRangesFeature extends Disposable {
|
||||
const d = derived(reader => /** @description hiddenOriginalRangeStart */ r.getHiddenOriginalRange(reader).startLineNumber - 1);
|
||||
const origVz = new PlaceholderViewZone(d, 24);
|
||||
origViewZones.push(origVz);
|
||||
store.add(new CollapsedCodeOverlayWidget(this._editors.original, origVz, r, r.originalRange, !sideBySide, modifiedOutlineSource, l => this._diffModel.get()!.ensureOriginalLineIsVisible(l, undefined), this._options));
|
||||
store.add(new CollapsedCodeOverlayWidget(this._editors.original, origVz, r, r.originalRange, !sideBySide, modifiedOutlineSource, l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, undefined), this._options));
|
||||
}
|
||||
{
|
||||
const d = derived(reader => /** @description hiddenModifiedRangeStart */ r.getHiddenModifiedRange(reader).startLineNumber - 1);
|
||||
@@ -250,7 +250,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget {
|
||||
private readonly _nodes = h('div.diff-hidden-lines', [
|
||||
h('div.top@top', { title: localize('diff.hiddenLines.top', 'Click or drag to show more above') }),
|
||||
h('div.center@content', { style: { display: 'flex' } }, [
|
||||
h('div@first', { style: { display: 'flex', justifyContent: 'center', alignItems: 'center' } },
|
||||
h('div@first', { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', flexShrink: '0' } },
|
||||
[$('a', { title: localize('showAll', 'Show all'), role: 'button', onclick: () => { this.showAll(); } }, ...renderLabelWithIcons('$(unfold)'))]
|
||||
),
|
||||
h('div@others', { style: { display: 'flex', justifyContent: 'center', alignItems: 'center' } }),
|
||||
@@ -265,7 +265,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget {
|
||||
private readonly _unchangedRegionRange: LineRange,
|
||||
private readonly hide: boolean,
|
||||
private readonly _modifiedOutlineSource: OutlineSource,
|
||||
private readonly _revealHiddenLine: (lineNumber: number) => void,
|
||||
private readonly _revealModifiedHiddenLine: (lineNumber: number) => void,
|
||||
private readonly _options: DiffEditorOptions,
|
||||
) {
|
||||
const root = h('div.diff-hidden-lines-widget');
|
||||
@@ -365,7 +365,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget {
|
||||
const children: HTMLElement[] = [];
|
||||
if (!this.hide) {
|
||||
const lineCount = _unchangedRegion.getHiddenModifiedRange(reader).length;
|
||||
const linesHiddenText = localize('hiddenLines', '{0} Hidden Lines', lineCount);
|
||||
const linesHiddenText = localize('hiddenLines', '{0} hidden lines', lineCount);
|
||||
const span = $('span', { title: localize('diff.hiddenLines.expandAll', 'Double click to unfold') }, linesHiddenText);
|
||||
span.addEventListener('dblclick', e => {
|
||||
if (e.button !== 0) { return; }
|
||||
@@ -396,7 +396,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget {
|
||||
]).root;
|
||||
children.push(divItem);
|
||||
divItem.onclick = () => {
|
||||
this._revealHiddenLine(item.startLineNumber);
|
||||
this._revealModifiedHiddenLine(item.startLineNumber);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { ValidDiffEditorBaseOptions } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export const diffEditorDefaultOptions: ValidDiffEditorBaseOptions = {
|
||||
export const diffEditorDefaultOptions = {
|
||||
enableSplitViewResizing: true,
|
||||
splitViewDefaultRatio: 0.5,
|
||||
renderSideBySide: true,
|
||||
@@ -34,4 +34,4 @@ export const diffEditorDefaultOptions: ValidDiffEditorBaseOptions = {
|
||||
onlyShowAccessibleDiffViewer: false,
|
||||
renderSideBySideInlineBreakpoint: 900,
|
||||
useInlineViewWhenSpaceIsLimited: true,
|
||||
};
|
||||
} satisfies ValidDiffEditorBaseOptions;
|
||||
|
||||
@@ -151,53 +151,53 @@ const editorConfiguration: IConfigurationNode = {
|
||||
},
|
||||
'diffEditor.maxComputationTime': {
|
||||
type: 'number',
|
||||
default: 5000,
|
||||
default: diffEditorDefaultOptions.maxComputationTime,
|
||||
description: nls.localize('maxComputationTime', "Timeout in milliseconds after which diff computation is cancelled. Use 0 for no timeout.")
|
||||
},
|
||||
'diffEditor.maxFileSize': {
|
||||
type: 'number',
|
||||
default: 50,
|
||||
default: diffEditorDefaultOptions.maxFileSize,
|
||||
description: nls.localize('maxFileSize', "Maximum file size in MB for which to compute diffs. Use 0 for no limit.")
|
||||
},
|
||||
'diffEditor.renderSideBySide': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
default: diffEditorDefaultOptions.renderSideBySide,
|
||||
description: nls.localize('sideBySide', "Controls whether the diff editor shows the diff side by side or inline.")
|
||||
},
|
||||
'diffEditor.renderSideBySideInlineBreakpoint': {
|
||||
type: 'number',
|
||||
default: true,
|
||||
default: diffEditorDefaultOptions.renderSideBySideInlineBreakpoint,
|
||||
description: nls.localize('renderSideBySideInlineBreakpoint', "If the diff editor width is smaller than this value, the inline view is used.")
|
||||
},
|
||||
'diffEditor.useInlineViewWhenSpaceIsLimited': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
default: diffEditorDefaultOptions.useInlineViewWhenSpaceIsLimited,
|
||||
description: nls.localize('useInlineViewWhenSpaceIsLimited', "If enabled and the editor width is too small, the inline view is used.")
|
||||
},
|
||||
'diffEditor.renderMarginRevertIcon': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
default: diffEditorDefaultOptions.renderMarginRevertIcon,
|
||||
description: nls.localize('renderMarginRevertIcon', "When enabled, the diff editor shows arrows in its glyph margin to revert changes.")
|
||||
},
|
||||
'diffEditor.ignoreTrimWhitespace': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
default: diffEditorDefaultOptions.ignoreTrimWhitespace,
|
||||
description: nls.localize('ignoreTrimWhitespace', "When enabled, the diff editor ignores changes in leading or trailing whitespace.")
|
||||
},
|
||||
'diffEditor.renderIndicators': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
default: diffEditorDefaultOptions.renderIndicators,
|
||||
description: nls.localize('renderIndicators', "Controls whether the diff editor shows +/- indicators for added/removed changes.")
|
||||
},
|
||||
'diffEditor.codeLens': {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
default: diffEditorDefaultOptions.diffCodeLens,
|
||||
description: nls.localize('codeLens', "Controls whether the editor shows CodeLens.")
|
||||
},
|
||||
'diffEditor.wordWrap': {
|
||||
type: 'string',
|
||||
enum: ['off', 'on', 'inherit'],
|
||||
default: 'inherit',
|
||||
default: diffEditorDefaultOptions.diffWordWrap,
|
||||
markdownEnumDescriptions: [
|
||||
nls.localize('wordWrap.off', "Lines will never wrap."),
|
||||
nls.localize('wordWrap.on', "Lines will wrap at the viewport width."),
|
||||
@@ -239,7 +239,7 @@ const editorConfiguration: IConfigurationNode = {
|
||||
},
|
||||
'diffEditor.experimental.showMoves': {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
default: diffEditorDefaultOptions.experimental.showMoves,
|
||||
markdownDescription: nls.localize('showMoves', "Controls whether the diff editor should show detected code moves. Only works when {0} is set.", '`#diffEditor.experimental.useVersion2#`')
|
||||
},
|
||||
'diffEditor.experimental.useVersion2': {
|
||||
@@ -250,7 +250,7 @@ const editorConfiguration: IConfigurationNode = {
|
||||
},
|
||||
'diffEditor.experimental.showEmptyDecorations': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
default: diffEditorDefaultOptions.experimental.showEmptyDecorations,
|
||||
description: nls.localize('showEmptyDecorations', "Controls whether the diff editor shows empty decorations to see where characters got inserted or deleted."),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2799,7 +2799,7 @@ class EditorStickyScroll extends BaseEditorOption<EditorOption.stickyScroll, IEd
|
||||
'editor.stickyScroll.scrollWithEditor': {
|
||||
type: 'boolean',
|
||||
default: defaults.scrollWithEditor,
|
||||
description: nls.localize('editor.stickyScroll.scrollWithEditor', "When enabled it is possible to scroll the sticky scroll widget with the editor horizontal scrollbar.")
|
||||
description: nls.localize('editor.stickyScroll.scrollWithEditor', "Enable scrolling of the sticky scroll widget with the editor's horizontal scrollbar.")
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { compareBy, equals, findLastIndex, numberComparator, reverseOrder } from 'vs/base/common/arrays';
|
||||
import { Comparator, CompareResult, compareBy, equals, findLastIndex, numberComparator, reverseOrder } from 'vs/base/common/arrays';
|
||||
import { assertFn, checkAdjacentItems } from 'vs/base/common/assert';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { SetMap } from 'vs/base/common/collections';
|
||||
import { BugIndicatingError } from 'vs/base/common/errors';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
@@ -302,7 +303,7 @@ export class AdvancedLinesDiffComputer implements ILinesDiffComputer {
|
||||
if (moves.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const joinedMoves = [moves[0]];
|
||||
let joinedMoves = [moves[0]];
|
||||
for (let i = 1; i < moves.length; i++) {
|
||||
const last = joinedMoves[joinedMoves.length - 1];
|
||||
const current = moves[i];
|
||||
@@ -324,6 +325,19 @@ export class AdvancedLinesDiffComputer implements ILinesDiffComputer {
|
||||
joinedMoves.push(current);
|
||||
}
|
||||
|
||||
// Ignore non moves
|
||||
const originalChanges = MonotonousFinder.createOfSorted(changes, c => c.originalRange.endLineNumberExclusive, numberComparator);
|
||||
joinedMoves = joinedMoves.filter(m => {
|
||||
const diffBeforeOriginalMove = originalChanges.findLastItemBeforeOrEqual(m.original.startLineNumber)
|
||||
|| new LineRangeMapping(new LineRange(1, 1), new LineRange(1, 1), []);
|
||||
|
||||
const modifiedDistToPrevDiff = m.modified.startLineNumber - diffBeforeOriginalMove.modifiedRange.endLineNumberExclusive;
|
||||
const originalDistToPrevDiff = m.original.startLineNumber - diffBeforeOriginalMove.originalRange.endLineNumberExclusive;
|
||||
|
||||
const differentDistances = modifiedDistToPrevDiff !== originalDistToPrevDiff;
|
||||
return differentDistances;
|
||||
});
|
||||
|
||||
const fullMoves = joinedMoves.map(m => {
|
||||
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
|
||||
m.original.toOffsetRange(),
|
||||
@@ -366,6 +380,60 @@ export class AdvancedLinesDiffComputer implements ILinesDiffComputer {
|
||||
}
|
||||
}
|
||||
|
||||
class MonotonousFinder<TItem, TDomain> {
|
||||
public static create<TItem, TDomain>(
|
||||
items: TItem[],
|
||||
itemToDomain: (item: TItem) => TDomain,
|
||||
domainComparator: Comparator<TDomain>,
|
||||
): MonotonousFinder<TItem, TDomain> {
|
||||
items.sort((a, b) => domainComparator(itemToDomain(a), itemToDomain(b)));
|
||||
return new MonotonousFinder(items, itemToDomain, domainComparator);
|
||||
}
|
||||
|
||||
public static createOfSorted<TItem, TDomain>(
|
||||
items: TItem[],
|
||||
itemToDomain: (item: TItem) => TDomain,
|
||||
domainComparator: Comparator<TDomain>,
|
||||
): MonotonousFinder<TItem, TDomain> {
|
||||
return new MonotonousFinder(items, itemToDomain, domainComparator);
|
||||
}
|
||||
|
||||
private _currentIdx = 0; // All values with index lower than this are smaller than or equal to _lastValue and vice versa.
|
||||
private _lastValue: TDomain | undefined = undefined; // Represents a smallest value.
|
||||
private _hasLastValue = false;
|
||||
|
||||
private constructor(
|
||||
private readonly _items: TItem[],
|
||||
private readonly _itemToDomain: (item: TItem) => TDomain,
|
||||
private readonly _domainComparator: Comparator<TDomain>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes the values are monotonously increasing.
|
||||
*/
|
||||
findLastItemBeforeOrEqual(value: TDomain): TItem | undefined {
|
||||
if (this._hasLastValue && CompareResult.isLessThan(this._domainComparator(value, this._lastValue!))) {
|
||||
// Values must be monotonously increasing
|
||||
throw new BugIndicatingError();
|
||||
}
|
||||
this._lastValue = value;
|
||||
this._hasLastValue = true;
|
||||
|
||||
while (
|
||||
this._currentIdx < this._items.length
|
||||
&& CompareResult.isLessThanOrEqual(this._domainComparator(
|
||||
this._itemToDomain(this._items[this._currentIdx]),
|
||||
value
|
||||
))
|
||||
) {
|
||||
this._currentIdx++;
|
||||
}
|
||||
|
||||
return this._currentIdx === 0 ? undefined : this._items[this._currentIdx - 1];
|
||||
}
|
||||
}
|
||||
|
||||
function intersectRanges(ranges1: LineRange[], ranges2: LineRange[]): LineRange[] {
|
||||
const result: LineRange[] = [];
|
||||
|
||||
@@ -839,16 +907,15 @@ export class LinesSliceCharSequence implements ISequence {
|
||||
}
|
||||
|
||||
public extendToFullLines(range: OffsetRange): OffsetRange {
|
||||
const firstIdx = findLastIdxMonotonous(this.firstCharOffsetByLineMinusOne, x => x <= range.start);
|
||||
const lastIdx = findFirstIdxMonotonous(this.firstCharOffsetByLineMinusOne, x => range.endExclusive <= x);
|
||||
|
||||
const start = firstIdx === -1 ? 0 : this.firstCharOffsetByLineMinusOne[firstIdx];
|
||||
const end = lastIdx === this.firstCharOffsetByLineMinusOne.length ? this.elements.length : this.firstCharOffsetByLineMinusOne[lastIdx];
|
||||
const start = findLastMonotonous(this.firstCharOffsetByLineMinusOne, x => x <= range.start) ?? 0;
|
||||
const end = findFirstMonotonous(this.firstCharOffsetByLineMinusOne, x => range.endExclusive <= x) ?? this.elements.length;
|
||||
return new OffsetRange(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`!
|
||||
*
|
||||
* @returns -1 if predicate is false for all items
|
||||
*/
|
||||
function findLastIdxMonotonous<T>(arr: T[], predicate: (item: T) => boolean): number {
|
||||
@@ -865,7 +932,14 @@ function findLastIdxMonotonous<T>(arr: T[], predicate: (item: T) => boolean): nu
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
export function findLastMonotonous<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
|
||||
const idx = findLastIdxMonotonous(arr, predicate);
|
||||
return idx === -1 ? undefined : arr[idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* `arr.map(predicate)` must be like `[false, ..., false, true, ..., true]`!
|
||||
*
|
||||
* @returns arr.length if predicate is false for all items
|
||||
*/
|
||||
function findFirstIdxMonotonous<T>(arr: T[], predicate: (item: T) => boolean): number {
|
||||
@@ -882,6 +956,11 @@ function findFirstIdxMonotonous<T>(arr: T[], predicate: (item: T) => boolean): n
|
||||
return i;
|
||||
}
|
||||
|
||||
export function findFirstMonotonous<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
|
||||
const idx = findFirstIdxMonotonous(arr, predicate);
|
||||
return idx === arr.length ? undefined : arr[idx];
|
||||
}
|
||||
|
||||
function isWordChar(charCode: number): boolean {
|
||||
return charCode >= CharCode.a && charCode <= CharCode.z
|
||||
|| charCode >= CharCode.A && charCode <= CharCode.Z
|
||||
|
||||
@@ -1114,6 +1114,9 @@ export module StandaloneServices {
|
||||
serviceCollection.set(IInstantiationService, instantiationService);
|
||||
|
||||
export function get<T>(serviceId: ServiceIdentifier<T>): T {
|
||||
if (!initialized) {
|
||||
initialize({});
|
||||
}
|
||||
const r = serviceCollection.get(serviceId);
|
||||
if (!r) {
|
||||
throw new Error('Missing service ' + serviceId);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { createStaticIPCHandle } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
@@ -68,6 +69,9 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
|
||||
@memoize
|
||||
get useCodeCache(): boolean { return !!this.codeCachePath; }
|
||||
|
||||
@memoize
|
||||
override get userRoamingDataHome(): URI { return this.appSettingsHome; }
|
||||
|
||||
unsetSnapExportedVariables() {
|
||||
if (!isLinux) {
|
||||
return;
|
||||
|
||||
@@ -152,12 +152,13 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extensionId, this.productService.version));
|
||||
}
|
||||
|
||||
const result = await this.installExtensions([{ manifest, extension: location, options }]);
|
||||
if (result[0]?.local) {
|
||||
return result[0]?.local;
|
||||
const results = await this.installExtensions([{ manifest, extension: location, options }]);
|
||||
const result = results.find(({ identifier }) => areSameExtensions(identifier, { id: extensionId }));
|
||||
if (result?.local) {
|
||||
return result.local;
|
||||
}
|
||||
if (result[0]?.error) {
|
||||
throw result[0].error;
|
||||
if (result?.error) {
|
||||
throw result.error;
|
||||
}
|
||||
throw toExtensionManagementError(new Error(`Unknown error while installing extension ${extensionId}`));
|
||||
} finally {
|
||||
|
||||
@@ -113,6 +113,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
when: ContextKeyExpr.and(
|
||||
ContextKeyExpr.has(HistoryNavigationWidgetFocusContext),
|
||||
ContextKeyExpr.equals(HistoryNavigationBackwardsEnablementContext, true),
|
||||
ContextKeyExpr.not('isComposing'),
|
||||
historyNavigationVisible.isEqualTo(false),
|
||||
),
|
||||
primary: KeyCode.UpArrow,
|
||||
@@ -128,6 +129,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
when: ContextKeyExpr.and(
|
||||
ContextKeyExpr.has(HistoryNavigationWidgetFocusContext),
|
||||
ContextKeyExpr.equals(HistoryNavigationForwardsEnablementContext, true),
|
||||
ContextKeyExpr.not('isComposing'),
|
||||
historyNavigationVisible.isEqualTo(false),
|
||||
),
|
||||
primary: KeyCode.DownArrow,
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface IQuickInputOptions {
|
||||
renderers: IListRenderer<T, any>[],
|
||||
options: IListOptions<T>,
|
||||
): List<T>;
|
||||
hoverDelegate: IHoverDelegate;
|
||||
hoverDelegate?: IHoverDelegate;
|
||||
styles: IQuickInputStyles;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,12 +83,13 @@ export class QuickInputController extends Disposable {
|
||||
|
||||
const titleBar = dom.append(container, $('.quick-input-titlebar'));
|
||||
|
||||
const leftActionBar = this._register(new ActionBar(titleBar));
|
||||
const actionBarOption = this.options.hoverDelegate ? { hoverDelegate: this.options.hoverDelegate } : undefined;
|
||||
const leftActionBar = this._register(new ActionBar(titleBar, actionBarOption));
|
||||
leftActionBar.domNode.classList.add('quick-input-left-action-bar');
|
||||
|
||||
const title = dom.append(titleBar, $('.quick-input-title'));
|
||||
|
||||
const rightActionBar = this._register(new ActionBar(titleBar));
|
||||
const rightActionBar = this._register(new ActionBar(titleBar, actionBarOption));
|
||||
rightActionBar.domNode.classList.add('quick-input-right-action-bar');
|
||||
|
||||
const headerContainer = dom.append(container, $('.quick-input-header'));
|
||||
|
||||
@@ -541,46 +541,49 @@ export class QuickInputList {
|
||||
}
|
||||
}));
|
||||
|
||||
const delayer = new ThrottledDelayer(options.hoverDelegate.delay);
|
||||
// onMouseOver triggers every time a new element has been moused over
|
||||
// even if it's on the same list item.
|
||||
this.disposables.push(this.list.onMouseOver(async e => {
|
||||
// If we hover over an anchor element, we don't want to show the hover because
|
||||
// the anchor may have a tooltip that we want to show instead.
|
||||
if (e.browserEvent.target instanceof HTMLAnchorElement) {
|
||||
delayer.cancel();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
// anchors are an exception as called out above so we skip them here
|
||||
!(e.browserEvent.relatedTarget instanceof HTMLAnchorElement) &&
|
||||
// check if the mouse is still over the same element
|
||||
dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await delayer.trigger(async () => {
|
||||
if (e.element) {
|
||||
this.showHover(e.element);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore cancellation errors due to mouse out
|
||||
if (!isCancellationError(e)) {
|
||||
throw e;
|
||||
if (options.hoverDelegate) {
|
||||
const delayer = new ThrottledDelayer(options.hoverDelegate.delay);
|
||||
// onMouseOver triggers every time a new element has been moused over
|
||||
// even if it's on the same list item.
|
||||
this.disposables.push(this.list.onMouseOver(async e => {
|
||||
// If we hover over an anchor element, we don't want to show the hover because
|
||||
// the anchor may have a tooltip that we want to show instead.
|
||||
if (e.browserEvent.target instanceof HTMLAnchorElement) {
|
||||
delayer.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.list.onMouseOut(e => {
|
||||
// onMouseOut triggers every time a new element has been moused over
|
||||
// even if it's on the same list item. We only want one event, so we
|
||||
// check if the mouse is still over the same element.
|
||||
if (dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)) {
|
||||
return;
|
||||
}
|
||||
delayer.cancel();
|
||||
}));
|
||||
if (
|
||||
// anchors are an exception as called out above so we skip them here
|
||||
!(e.browserEvent.relatedTarget instanceof HTMLAnchorElement) &&
|
||||
// check if the mouse is still over the same element
|
||||
dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await delayer.trigger(async () => {
|
||||
if (e.element) {
|
||||
this.showHover(e.element);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore cancellation errors due to mouse out
|
||||
if (!isCancellationError(e)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.list.onMouseOut(e => {
|
||||
// onMouseOut triggers every time a new element has been moused over
|
||||
// even if it's on the same list item. We only want one event, so we
|
||||
// check if the mouse is still over the same element.
|
||||
if (dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)) {
|
||||
return;
|
||||
}
|
||||
delayer.cancel();
|
||||
}));
|
||||
this.disposables.push(delayer);
|
||||
}
|
||||
this.disposables.push(this._listElementChecked.event(_ => this.fireCheckedEvents()));
|
||||
this.disposables.push(
|
||||
this._onChangedAllVisibleChecked,
|
||||
@@ -590,8 +593,7 @@ export class QuickInputList {
|
||||
this._onButtonTriggered,
|
||||
this._onSeparatorButtonTriggered,
|
||||
this._onLeave,
|
||||
this._onKeyDown,
|
||||
delayer
|
||||
this._onKeyDown
|
||||
);
|
||||
}
|
||||
|
||||
@@ -839,10 +841,14 @@ export class QuickInputList {
|
||||
* @param element The element to show the hover for
|
||||
*/
|
||||
private showHover(element: IListElement): void {
|
||||
if (this.options.hoverDelegate === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this._lastHover && !this._lastHover.isDisposed) {
|
||||
this.options.hoverDelegate.onDidHideHover?.();
|
||||
this._lastHover?.dispose();
|
||||
}
|
||||
|
||||
if (!element.element || !element.saneTooltip) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -86,12 +86,6 @@ export class QuickInputService extends Themable implements IQuickInputService {
|
||||
renderers: IListRenderer<T, any>[],
|
||||
options: IWorkbenchListOptions<T>
|
||||
) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List<T>,
|
||||
hoverDelegate: {
|
||||
showHover(options, focus) {
|
||||
return undefined;
|
||||
},
|
||||
delay: 200
|
||||
},
|
||||
styles: this.computeStyles()
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ export const enum TerminalSettingId {
|
||||
EnableImages = 'terminal.integrated.enableImages',
|
||||
SmoothScrolling = 'terminal.integrated.smoothScrolling',
|
||||
IgnoreBracketedPasteMode = 'terminal.integrated.ignoreBracketedPasteMode',
|
||||
FocusAfterRun = 'terminal.integrated.focusAfterRun',
|
||||
|
||||
// Debug settings that are hidden from user
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Extensions as ViewletExtensions, PaneCompositeRegistry } from 'vs/workb
|
||||
import { CustomTreeView, RawCustomTreeViewContextKey, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { Extensions as ViewContainerExtensions, ICustomTreeViewDescriptor, ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ResolvableTreeItem, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { Extensions as ViewContainerExtensions, ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ResolvableTreeItem, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
|
||||
@@ -530,14 +530,14 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
const viewDescriptor = <ICustomTreeViewDescriptor>{
|
||||
const viewDescriptor: ICustomViewDescriptor = {
|
||||
type: type,
|
||||
ctorDescriptor: type === ViewType.Tree ? new SyncDescriptor(TreeViewPane) : new SyncDescriptor(WebviewViewPane),
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
when: ContextKeyExpr.deserialize(item.when),
|
||||
containerIcon: icon || viewContainer?.icon,
|
||||
containerTitle: item.contextualTitle || viewContainer?.title,
|
||||
containerTitle: item.contextualTitle || (viewContainer && (typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value)),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: viewContainer?.id !== REMOTE,
|
||||
treeView: type === ViewType.Tree ? this.instantiationService.createInstance(CustomTreeView, item.id, item.name, extension.description.identifier.value) : undefined,
|
||||
@@ -587,7 +587,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
if (removedViews.length) {
|
||||
this.viewsRegistry.deregisterViews(removedViews, viewContainer);
|
||||
for (const view of removedViews) {
|
||||
const anyView = view as ICustomTreeViewDescriptor;
|
||||
const anyView = view as ICustomViewDescriptor;
|
||||
if (anyView.treeView) {
|
||||
anyView.treeView.dispose();
|
||||
}
|
||||
|
||||
@@ -246,6 +246,9 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
$setVisible(treeViewId: string, isVisible: boolean): void {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
if (!isVisible) {
|
||||
return;
|
||||
}
|
||||
throw new NoTreeViewError(treeViewId);
|
||||
}
|
||||
treeView.setVisible(isVisible);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import * as nativeWatchdog from 'native-watchdog';
|
||||
import * as net from 'net';
|
||||
import * as minimist from 'minimist';
|
||||
import * as dns from 'dns';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import type { MessagePortMain } from 'vs/base/parts/sandbox/node/electronTypes';
|
||||
import { isCancellationError, isSigPipeError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
@@ -47,15 +46,6 @@ interface ParsedExtHostArgs {
|
||||
}
|
||||
})();
|
||||
|
||||
// TODO(deepak1556): Remove this once
|
||||
// https://github.com/electron/electron/pull/39376
|
||||
// is available. The following API call is needed to get our
|
||||
// remote integration tests to pass.
|
||||
(function configureDnsResultOrder() {
|
||||
// Refs https://github.com/microsoft/vscode/issues/189805
|
||||
dns.setDefaultResultOrder('ipv4first');
|
||||
})();
|
||||
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: [
|
||||
'transformURIs',
|
||||
|
||||
@@ -94,6 +94,7 @@ class InspectContextKeysAction extends Action2 {
|
||||
|
||||
interface IScreencastKeyboardOptions {
|
||||
readonly showKeys?: boolean;
|
||||
readonly showKeybindings?: boolean;
|
||||
readonly showCommands?: boolean;
|
||||
readonly showCommandGroups?: boolean;
|
||||
readonly showSingleEditorCursorMoves?: boolean;
|
||||
@@ -315,7 +316,7 @@ class ToggleScreencastModeAction extends Action2 {
|
||||
append(keyboardMarker, $('span.title', {}, `${commandAndGroupLabel} `));
|
||||
}
|
||||
|
||||
if (options.showKeys ?? true) {
|
||||
if ((options.showKeys ?? true) || (command && (options.showKeybindings ?? true))) {
|
||||
// Fix label for arrow keys
|
||||
keyLabel = keyLabel?.replace('UpArrow', '↑')
|
||||
?.replace('DownArrow', '↓')
|
||||
@@ -435,6 +436,11 @@ configurationRegistry.registerConfiguration({
|
||||
default: true,
|
||||
description: localize('screencastMode.keyboardOptions.showKeys', "Show raw keys.")
|
||||
},
|
||||
'showKeybindings': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: localize('screencastMode.keyboardOptions.showKeybindings', "Show keyboard shortcuts.")
|
||||
},
|
||||
'showCommands': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
@@ -453,6 +459,7 @@ configurationRegistry.registerConfiguration({
|
||||
},
|
||||
default: {
|
||||
'showKeys': true,
|
||||
'showKeybindings': true,
|
||||
'showCommands': true,
|
||||
'showCommandGroups': false,
|
||||
'showSingleEditorCursorMoves': true
|
||||
|
||||
@@ -2264,6 +2264,11 @@ abstract class AbstractCreateEditorGroupAction extends Action2 {
|
||||
override async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
// We intentionally do not want the new group to be focussed so that
|
||||
// a user can have keyboard focus e.g. in a tree/list, open a new
|
||||
// editor group that is active and then arrow-up/down in the tree/list
|
||||
// to pick an editor to open in that group
|
||||
|
||||
editorGroupService.addGroup(editorGroupService.activeGroup, this.direction, { activate: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,21 +515,12 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
addGroup(location: IEditorGroupView | GroupIdentifier, direction: GroupDirection, options?: IAddGroupOptions): IEditorGroupView {
|
||||
const locationView = this.assertGroupView(location);
|
||||
|
||||
const restoreFocus = this.shouldRestoreFocus(locationView.element);
|
||||
|
||||
const group = this.doAddGroup(locationView, direction);
|
||||
|
||||
if (options?.activate) {
|
||||
this.doSetGroupActive(group);
|
||||
}
|
||||
|
||||
// Restore focus if we had it previously after completing the grid
|
||||
// operation. That operation might cause reparenting of grid views
|
||||
// which moves focus to the <body> element otherwise.
|
||||
if (restoreFocus) {
|
||||
locationView.focus();
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
|
||||
@@ -2077,6 +2077,10 @@ registerThemingParticipant((theme, collector) => {
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:focus {
|
||||
outline-style: dashed;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active {
|
||||
outline: 1px dotted;
|
||||
outline-offset: -5px;
|
||||
|
||||
@@ -68,9 +68,19 @@ export function getNotificationFromContext(listService: IListService, context?:
|
||||
|
||||
const list = listService.lastFocusedList;
|
||||
if (list instanceof WorkbenchList) {
|
||||
const focusedElement = list.getFocusedElements()[0];
|
||||
if (isNotificationViewItem(focusedElement)) {
|
||||
return focusedElement;
|
||||
let element = list.getFocusedElements()[0];
|
||||
if (!isNotificationViewItem(element)) {
|
||||
if (list.isDOMFocused()) {
|
||||
// the notification list might have received focus
|
||||
// via keyboard and might not have a focussed element.
|
||||
// in that case just return the first element
|
||||
// https://github.com/microsoft/vscode/issues/191705
|
||||
element = list.element(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotificationViewItem(element)) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ export class NotificationsListDelegate implements IListVirtualDelegate<INotifica
|
||||
if (isNonEmptyArray(notification.actions && notification.actions.secondary)) {
|
||||
actions++; // secondary actions
|
||||
}
|
||||
this.offsetHelper.style.width = `${450 /* notifications container width */ - (10 /* padding */ + 26 /* severity icon */ + (actions * (24 + 8)) /* 24px (+8px padding) per action */ - 4 /* 4px less padding for last action */)}px`;
|
||||
this.offsetHelper.style.width = `${450 /* notifications container width */ - (10 /* padding */ + 30 /* severity icon */ + (actions * 30) /* actions */ - (Math.max(actions - 1, 0) * 4) /* less padding for actions > 1 */)}px`;
|
||||
|
||||
// Render message into offset helper
|
||||
const renderedMessage = NotificationMessageRenderer.render(notification.message);
|
||||
|
||||
@@ -533,7 +533,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
|
||||
|
||||
// Window
|
||||
|
||||
let windowTitleDescription = localize('windowTitle', "Controls the window title based on the active editor. Variables are substituted based on the context:");
|
||||
let windowTitleDescription = localize('windowTitle', "Controls the window title based on the current context such as the opened workspace or active editor. Variables are substituted based on the context:");
|
||||
windowTitleDescription += '\n- ' + [
|
||||
localize('activeEditorShort', "`${activeEditorShort}`: the file name (e.g. myFile.txt)."),
|
||||
localize('activeEditorMedium', "`${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)."),
|
||||
|
||||
@@ -342,7 +342,7 @@ export class Workbench extends Layout {
|
||||
|
||||
// Create Parts
|
||||
for (const { id, role, classes, options } of [
|
||||
{ id: Parts.TITLEBAR_PART, role: 'contentinfo', classes: ['titlebar'] },
|
||||
{ id: Parts.TITLEBAR_PART, role: 'none', classes: ['titlebar'] },
|
||||
{ id: Parts.BANNER_PART, role: 'banner', classes: ['banner'] },
|
||||
{ id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892
|
||||
{ id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] },
|
||||
|
||||
@@ -300,18 +300,12 @@ export interface IViewDescriptor {
|
||||
readonly openCommandActionDescriptor?: OpenCommandActionDescriptor;
|
||||
}
|
||||
|
||||
export interface ICustomTreeViewDescriptor extends ITreeViewDescriptor {
|
||||
export interface ICustomViewDescriptor extends IViewDescriptor {
|
||||
readonly extensionId: ExtensionIdentifier;
|
||||
readonly originalContainerId: string;
|
||||
readonly treeView?: ITreeView;
|
||||
}
|
||||
|
||||
export interface ICustomWebviewViewDescriptor extends IViewDescriptor {
|
||||
readonly extensionId: ExtensionIdentifier;
|
||||
readonly originalContainerId: string;
|
||||
}
|
||||
|
||||
export type ICustomViewDescriptor = ICustomTreeViewDescriptor | ICustomWebviewViewDescriptor;
|
||||
|
||||
export interface IViewDescriptorRef {
|
||||
viewDescriptor: IViewDescriptor;
|
||||
index: number;
|
||||
|
||||
@@ -21,8 +21,8 @@ export const accessibleViewCurrentProviderId = new RawContextKey<string>('access
|
||||
* were better to live under workbench for discoverability.
|
||||
*/
|
||||
export const enum AccessibilityWorkbenchSettingId {
|
||||
ViewDimUnfocusedEnabled = 'workbench.view.dimUnfocused.enabled',
|
||||
ViewDimUnfocusedOpacity = 'workbench.view.dimUnfocused.opacity'
|
||||
DimUnfocusedEnabled = 'accessibility.dimUnfocused.enabled',
|
||||
DimUnfocusedOpacity = 'accessibility.dimUnfocused.opacity'
|
||||
}
|
||||
|
||||
export const enum ViewDimUnfocusedOpacityProperties {
|
||||
@@ -42,7 +42,7 @@ export const enum AccessibilityVerbositySettingId {
|
||||
Editor = 'accessibility.verbosity.editor',
|
||||
Hover = 'accessibility.verbosity.hover',
|
||||
Notification = 'accessibility.verbosity.notification',
|
||||
EditorUntitledHint = 'accessibility.verbosity.editor.untitledHint'
|
||||
EditorUntitledHint = 'accessibility.verbosity.untitledHint'
|
||||
}
|
||||
|
||||
export const enum AccessibleViewProviderId {
|
||||
@@ -107,7 +107,7 @@ const configuration: IConfigurationNode = {
|
||||
...baseProperty
|
||||
},
|
||||
[AccessibilityVerbositySettingId.EditorUntitledHint]: {
|
||||
description: localize('verbosity.editor.untitledhint', 'Provide information about relevant actions in an untitled text editor.'),
|
||||
description: localize('verbosity.untitledhint', 'Provide information about relevant actions in an untitled text editor.'),
|
||||
...baseProperty
|
||||
}
|
||||
}
|
||||
@@ -120,15 +120,15 @@ export function registerAccessibilityConfiguration() {
|
||||
registry.registerConfiguration({
|
||||
...workbenchConfigurationNodeBase,
|
||||
properties: {
|
||||
[AccessibilityWorkbenchSettingId.ViewDimUnfocusedEnabled]: {
|
||||
description: localize('dimUnfocusedEnabled', 'Whether to dim unfocused editors and terminals, making the focused view more obvious.'),
|
||||
[AccessibilityWorkbenchSettingId.DimUnfocusedEnabled]: {
|
||||
description: localize('dimUnfocusedEnabled', 'Whether to dim unfocused editors and terminals, which makes it more clear where typed input will go to. This works with the majority of editors with the notable exceptions of those that utilize iframes like notebooks and extension webview editors.'),
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
tags: ['accessibility'],
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
[AccessibilityWorkbenchSettingId.ViewDimUnfocusedOpacity]: {
|
||||
description: localize('dimUnfocusedOpacity', 'The opacity fraction (0.2 to 1.0) to use for unfocused editors and terminals. This will only take effect when {0} is enabled.', `\`#${AccessibilityWorkbenchSettingId.ViewDimUnfocusedEnabled}#\``),
|
||||
[AccessibilityWorkbenchSettingId.DimUnfocusedOpacity]: {
|
||||
markdownDescription: localize('dimUnfocusedOpacity', 'The opacity fraction (0.2 to 1.0) to use for unfocused editors and terminals. This will only take effect when {0} is enabled.', `\`#${AccessibilityWorkbenchSettingId.DimUnfocusedEnabled}#\``),
|
||||
type: 'number',
|
||||
minimum: ViewDimUnfocusedOpacityProperties.Minimum,
|
||||
maximum: ViewDimUnfocusedOpacityProperties.Maximum,
|
||||
|
||||
@@ -258,6 +258,10 @@ function getActionsFromNotification(notification: INotificationViewItem): IActio
|
||||
};
|
||||
}
|
||||
}
|
||||
const manageExtension = actions?.find(a => a.label.includes('Manage Extension'));
|
||||
if (manageExtension) {
|
||||
manageExtension.class = ThemeIcon.asClassName(Codicon.gear);
|
||||
}
|
||||
if (actions) {
|
||||
actions.push({ id: 'clearNotification', label: localize('clearNotification', "Clear Notification"), tooltip: localize('clearNotification', "Clear Notification"), run: () => notification.close(), enabled: true, class: ThemeIcon.asClassName(Codicon.clearAll) });
|
||||
}
|
||||
|
||||
@@ -506,9 +506,9 @@ class AccessibleView extends Disposable {
|
||||
let hint = '';
|
||||
const disableKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.DisableVerbosityHint, this._contextKeyService)?.getAriaLabel();
|
||||
if (disableKeybinding) {
|
||||
hint = localize('acessibleViewDisableHint', "Disable the aria label hint to open this ({0}).\n", disableKeybinding);
|
||||
hint = localize('acessibleViewDisableHint', "Disable accessibility verbosity for this feature ({0}). This will disable the hint to open the accessible view for example.\n", disableKeybinding);
|
||||
} else {
|
||||
hint = localize('accessibleViewDisableHintNoKb', "Add a keybinding for the command Disable Accessible View Hint to disable this hint.\n");
|
||||
hint = localize('accessibleViewDisableHintNoKb', "Add a keybinding for the command Disable Accessible View Hint, which disables accessibility verbosity for this feature.\n");
|
||||
}
|
||||
return hint;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ class AccessibleViewDisableHintAction extends Action2 {
|
||||
primary: KeyMod.Alt | KeyCode.F6,
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
},
|
||||
icon: Codicon.treeFilterClear,
|
||||
icon: Codicon.bellSlash,
|
||||
menu: [
|
||||
commandPalette,
|
||||
{
|
||||
|
||||
@@ -21,21 +21,23 @@ export class UnfocusedViewDimmingContribution extends Disposable implements IWor
|
||||
this._register(toDisposable(() => this._removeStyleElement()));
|
||||
|
||||
this._register(Event.runAndSubscribe(configurationService.onDidChangeConfiguration, e => {
|
||||
if (e && !e.affectsConfiguration(AccessibilityWorkbenchSettingId.ViewDimUnfocusedEnabled) && !e.affectsConfiguration(AccessibilityWorkbenchSettingId.ViewDimUnfocusedOpacity)) {
|
||||
if (e && !e.affectsConfiguration(AccessibilityWorkbenchSettingId.DimUnfocusedEnabled) && !e.affectsConfiguration(AccessibilityWorkbenchSettingId.DimUnfocusedOpacity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cssTextContent = '';
|
||||
|
||||
const enabled = ensureBoolean(configurationService.getValue(AccessibilityWorkbenchSettingId.ViewDimUnfocusedEnabled), false);
|
||||
const enabled = ensureBoolean(configurationService.getValue(AccessibilityWorkbenchSettingId.DimUnfocusedEnabled), false);
|
||||
if (enabled) {
|
||||
const opacity = clamp(
|
||||
ensureNumber(configurationService.getValue(AccessibilityWorkbenchSettingId.ViewDimUnfocusedOpacity), ViewDimUnfocusedOpacityProperties.Default),
|
||||
ensureNumber(configurationService.getValue(AccessibilityWorkbenchSettingId.DimUnfocusedOpacity), ViewDimUnfocusedOpacityProperties.Default),
|
||||
ViewDimUnfocusedOpacityProperties.Minimum,
|
||||
ViewDimUnfocusedOpacityProperties.Maximum
|
||||
);
|
||||
|
||||
if (opacity !== 1) {
|
||||
// These filter rules are more specific than may be expected as the `filter`
|
||||
// rule can cause problems if it's used inside the element like on editor hovers
|
||||
const rules = new Set<string>();
|
||||
const filterRule = `filter: opacity(${opacity});`;
|
||||
// Terminal tabs
|
||||
@@ -44,8 +46,18 @@ export class UnfocusedViewDimmingContribution extends Disposable implements IWor
|
||||
rules.add(`.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper:not(:focus-within) { ${filterRule} }`);
|
||||
// Text editors
|
||||
rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .monaco-editor { ${filterRule} }`);
|
||||
// Breadcrumbs
|
||||
rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .tabs-breadcrumbs { ${filterRule} }`);
|
||||
// Terminal editors
|
||||
rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .terminal-wrapper { ${filterRule} }`);
|
||||
// Settings editor
|
||||
rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .settings-editor { ${filterRule} }`);
|
||||
// Keybindings editor
|
||||
rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .keybindings-editor { ${filterRule} }`);
|
||||
// Editor placeholder (error case)
|
||||
rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .monaco-editor-pane-placeholder { ${filterRule} }`);
|
||||
// Welcome editor
|
||||
rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .gettingStartedContainer { ${filterRule} }`);
|
||||
cssTextContent = [...rules].join('\n');
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { ResourceMap, ResourceSet } from 'vs/base/common/map';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IBulkEditOptions, IBulkEditPreviewHandler, IBulkEditResult, IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
@@ -197,6 +197,8 @@ export class BulkEditService implements IBulkEditService {
|
||||
const candidate = this._editorService.activeTextEditorControl;
|
||||
if (isCodeEditor(candidate)) {
|
||||
codeEditor = candidate;
|
||||
} else if (isDiffEditor(candidate)) {
|
||||
codeEditor = candidate.getModifiedEditor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { tail } from 'vs/base/common/arrays';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
interface IFileOperation {
|
||||
uris: URI[];
|
||||
@@ -173,6 +174,9 @@ class CreateOperation implements IFileOperation {
|
||||
const undoes: DeleteEdit[] = [];
|
||||
|
||||
for (const edit of this._edits) {
|
||||
if (edit.newUri.scheme === Schemas.untitled) {
|
||||
continue; // ignore, will be handled by a later edit
|
||||
}
|
||||
if (edit.options.overwrite === undefined && edit.options.ignoreIfExists && await this._fileService.exists(edit.newUri)) {
|
||||
continue; // not overwriting, but ignoring, and the target file exists
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { RelatedContextItem, WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
import { ILanguageService } from 'vs/editor/common/languages/language';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { CopyAction } from 'vs/editor/contrib/clipboard/browser/clipboard';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
@@ -31,8 +33,6 @@ import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/comm
|
||||
import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { WorkspaceEdit, RelatedContextItem } from 'vs/editor/common/languages';
|
||||
|
||||
export interface IChatCodeBlockActionContext {
|
||||
code: string;
|
||||
@@ -92,6 +92,11 @@ export function registerChatCodeBlockActions() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.element.errorDetails?.responseIsFiltered) {
|
||||
// When run from command palette
|
||||
return;
|
||||
}
|
||||
|
||||
const clipboardService = accessor.get(IClipboardService);
|
||||
clipboardService.writeText(context.code);
|
||||
|
||||
@@ -183,6 +188,11 @@ export function registerChatCodeBlockActions() {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const textFileService = accessor.get(ITextFileService);
|
||||
|
||||
if (context.element.errorDetails?.responseIsFiltered) {
|
||||
// When run from command palette
|
||||
return;
|
||||
}
|
||||
|
||||
if (editorService.activeEditorPane?.getId() === NOTEBOOK_EDITOR_ID) {
|
||||
return this.handleNotebookEditor(accessor, editorService.activeEditorPane.getControl() as INotebookEditor, context);
|
||||
}
|
||||
@@ -312,6 +322,11 @@ export function registerChatCodeBlockActions() {
|
||||
}
|
||||
|
||||
override async runWithContext(accessor: ServicesAccessor, context: IChatCodeBlockActionContext) {
|
||||
if (context.element.errorDetails?.responseIsFiltered) {
|
||||
// When run from command palette
|
||||
return;
|
||||
}
|
||||
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const chatService = accessor.get(IChatService);
|
||||
editorService.openEditor(<IUntitledTextResourceEditorInput>{ contents: context.code, languageId: context.languageId, resource: undefined });
|
||||
@@ -358,6 +373,11 @@ export function registerChatCodeBlockActions() {
|
||||
}
|
||||
|
||||
override async runWithContext(accessor: ServicesAccessor, context: IChatCodeBlockActionContext) {
|
||||
if (context.element.errorDetails?.responseIsFiltered) {
|
||||
// When run from command palette
|
||||
return;
|
||||
}
|
||||
|
||||
const chatService = accessor.get(IChatService);
|
||||
const terminalService = accessor.get(ITerminalService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
|
||||
@@ -15,7 +15,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
|
||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatService, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
@@ -119,7 +119,7 @@ export function registerChatTitleActions() {
|
||||
id: MenuId.ChatMessageTitle,
|
||||
group: 'navigation',
|
||||
isHiddenByDefault: true,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CONTEXT_RESPONSE)
|
||||
when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED.negate())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/a
|
||||
import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
|
||||
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
|
||||
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatResponseMarkdownRenderData, IChatResponseRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
|
||||
@@ -268,10 +268,13 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
|
||||
templateData.titleToolbar.context = element;
|
||||
|
||||
const isFiltered = !!(isResponseVM(element) && element.errorDetails?.responseIsFiltered);
|
||||
CONTEXT_RESPONSE_FILTERED.bindTo(templateData.contextKeyService).set(isFiltered);
|
||||
|
||||
templateData.rowContainer.classList.toggle('interactive-request', isRequestVM(element));
|
||||
templateData.rowContainer.classList.toggle('interactive-response', isResponseVM(element));
|
||||
templateData.rowContainer.classList.toggle('interactive-welcome', isWelcomeVM(element));
|
||||
templateData.rowContainer.classList.toggle('filtered-response', !!(isResponseVM(element) && element.errorDetails?.responseIsFiltered));
|
||||
templateData.rowContainer.classList.toggle('filtered-response', isFiltered);
|
||||
templateData.username.textContent = element.username;
|
||||
|
||||
if (element.avatarIconUri) {
|
||||
@@ -449,9 +452,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
}
|
||||
|
||||
// Did this part's content change?
|
||||
else if (!isInteractiveProgressTreeData(part) && !isInteractiveProgressTreeData(renderedPart) && !renderedPart.isFullyRendered) {
|
||||
else if (!isInteractiveProgressTreeData(part) && !isInteractiveProgressTreeData(renderedPart)) {
|
||||
const wordCountResult = this.getDataForProgressiveRender(element, part, renderedPart);
|
||||
if (wordCountResult !== undefined) {
|
||||
// Check if there are any new words to render
|
||||
if (wordCountResult !== undefined && renderedPart.renderedWordCount !== wordCountResult?.actualWordCount) {
|
||||
partsToRender[index] = {
|
||||
renderedWordCount: wordCountResult.actualWordCount,
|
||||
lastRenderTime: Date.now(),
|
||||
@@ -535,6 +539,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
treeDisposables.add(tree.onDidChangeCollapseState(() => {
|
||||
this._onDidChangeItemHeight.fire({ element, height: templateData.rowContainer.offsetHeight });
|
||||
}));
|
||||
treeDisposables.add(tree.onContextMenu((e) => {
|
||||
e.browserEvent.preventDefault();
|
||||
e.browserEvent.stopPropagation();
|
||||
}));
|
||||
|
||||
tree.setInput(data).then(() => {
|
||||
if (!ref.isStale()) {
|
||||
@@ -969,6 +977,12 @@ class CodeBlockPart extends Disposable implements IChatResultCodeBlockPart {
|
||||
element: data.element,
|
||||
languageId: vscodeLanguageId
|
||||
};
|
||||
|
||||
if (isResponseVM(data.element) && data.element.errorDetails?.responseIsFiltered) {
|
||||
dom.hide(this.toolbar.getElement());
|
||||
} else {
|
||||
dom.show(this.toolbar.getElement());
|
||||
}
|
||||
}
|
||||
|
||||
private fixCodeText(text: string, languageId: string): string {
|
||||
@@ -1190,7 +1204,8 @@ class ChatListTreeRenderer implements ICompressibleTreeRenderer<IChatResponsePro
|
||||
}
|
||||
renderElement(element: ITreeNode<IChatResponseProgressFileTreeData, void>, index: number, templateData: IChatListTreeRendererTemplate, height: number | undefined): void {
|
||||
templateData.label.element.style.display = 'flex';
|
||||
if (!element.children.length) {
|
||||
const hasExtension = /\.[^/.]+$/.test(element.element.label);
|
||||
if (!element.children.length && hasExtension) {
|
||||
templateData.label.setFile(element.element.uri, {
|
||||
fileKind: FileKind.FILE,
|
||||
hidePath: true,
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
@@ -59,8 +60,9 @@ export class QuickChatService extends Disposable implements IQuickChatService {
|
||||
this.open(providerId, query);
|
||||
}
|
||||
}
|
||||
|
||||
open(providerId?: string, query?: string | undefined): void {
|
||||
if (this.focused) {
|
||||
if (this._input) {
|
||||
return this.focus();
|
||||
}
|
||||
|
||||
@@ -91,10 +93,13 @@ export class QuickChatService extends Disposable implements IQuickChatService {
|
||||
|
||||
// show needs to come after the quickpick is shown
|
||||
this._currentChat.render(this._container);
|
||||
} else {
|
||||
this._currentChat.show();
|
||||
}
|
||||
|
||||
disposableStore.add(this._input.onDidHide(() => {
|
||||
disposableStore.dispose();
|
||||
this._currentChat!.hide();
|
||||
this._input = undefined;
|
||||
this._onDidClose.fire();
|
||||
}));
|
||||
@@ -128,6 +133,8 @@ class QuickChat extends Disposable {
|
||||
private sash!: Sash;
|
||||
private model: ChatModel | undefined;
|
||||
private _currentQuery: string | undefined;
|
||||
private maintainScrollTimer: MutableDisposable<IDisposable> = this._register(new MutableDisposable<IDisposable>());
|
||||
private _deferUpdatingDynamicLayout: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly _options: IChatViewOptions,
|
||||
@@ -162,6 +169,30 @@ class QuickChat extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.widget.setVisible(false);
|
||||
// Maintain scroll position for a short time so that if the user re-shows the chat
|
||||
// the same scroll position will be used.
|
||||
this.maintainScrollTimer.value = disposableTimeout(() => {
|
||||
// At this point, clear this mutable disposable which will be our signal that
|
||||
// the timer has expired and we should stop maintaining scroll position
|
||||
this.maintainScrollTimer.clear();
|
||||
}, 30 * 1000); // 30 seconds
|
||||
}
|
||||
|
||||
show(): void {
|
||||
this.widget.setVisible(true);
|
||||
// If the mutable disposable is set, then we are keeping the existing scroll position
|
||||
// so we should not update the layout.
|
||||
if (this._deferUpdatingDynamicLayout) {
|
||||
this._deferUpdatingDynamicLayout = false;
|
||||
this.widget.updateDynamicChatTreeItemLayout(2, this.maxHeight);
|
||||
}
|
||||
if (!this.maintainScrollTimer.value) {
|
||||
this.widget.layoutDynamicChatTreeItemMode();
|
||||
}
|
||||
}
|
||||
|
||||
render(parent: HTMLElement): void {
|
||||
if (this.widget) {
|
||||
throw new Error('Cannot render quick chat twice');
|
||||
@@ -196,7 +227,14 @@ class QuickChat extends Disposable {
|
||||
|
||||
private registerListeners(parent: HTMLElement): void {
|
||||
this._register(this.layoutService.onDidLayout(() => {
|
||||
this.widget.updateDynamicChatTreeItemLayout(2, this.maxHeight);
|
||||
if (this.widget.visible) {
|
||||
this.widget.updateDynamicChatTreeItemLayout(2, this.maxHeight);
|
||||
} else {
|
||||
// If the chat is not visible, then we should defer updating the layout
|
||||
// because it relies on offsetHeight which only works correctly
|
||||
// when the chat is visible.
|
||||
this._deferUpdatingDynamicLayout = true;
|
||||
}
|
||||
}));
|
||||
this._register(this.widget.inputEditor.onDidChangeModelContent((e) => {
|
||||
this._currentQuery = this.widget.inputEditor.getValue();
|
||||
|
||||
@@ -77,9 +77,12 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
private container!: HTMLElement;
|
||||
|
||||
private bodyDimension: dom.Dimension | undefined;
|
||||
private visible = false;
|
||||
private visibleChangeCount = 0;
|
||||
private requestInProgress: IContextKey<boolean>;
|
||||
private _visible = false;
|
||||
public get visible() {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
private previousTreeScrollHeight: number = 0;
|
||||
|
||||
@@ -213,8 +216,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
this._onDidClear.fire();
|
||||
}
|
||||
|
||||
private onDidChangeItems() {
|
||||
if (this.tree && this.visible) {
|
||||
private onDidChangeItems(skipDynamicLayout?: boolean) {
|
||||
if (this.tree && this._visible) {
|
||||
const treeItems = (this.viewModel?.getItems() ?? [])
|
||||
.map(item => {
|
||||
return <ITreeElement<ChatTreeItem>>{
|
||||
@@ -239,7 +242,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
}
|
||||
});
|
||||
|
||||
if (this._dynamicMessageLayoutData) {
|
||||
if (!skipDynamicLayout && this._dynamicMessageLayoutData) {
|
||||
this.layoutDynamicChatTreeItemMode();
|
||||
}
|
||||
|
||||
@@ -261,7 +264,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
this.visible = visible;
|
||||
this._visible = visible;
|
||||
this.visibleChangeCount++;
|
||||
this.renderer.setVisible(visible);
|
||||
|
||||
@@ -269,8 +272,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
this._register(disposableTimeout(() => {
|
||||
// Progressive rendering paused while hidden, so start it up again.
|
||||
// Do it after a timeout because the container is not visible yet (it should be but offsetHeight returns 0 here)
|
||||
if (this.visible) {
|
||||
this.onDidChangeItems();
|
||||
if (this._visible) {
|
||||
this.onDidChangeItems(true);
|
||||
}
|
||||
}, 0));
|
||||
}
|
||||
@@ -540,6 +543,11 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
|
||||
const mutableDisposable = this._register(new MutableDisposable());
|
||||
this._register(this.tree.onDidScroll((e) => {
|
||||
// TODO@TylerLeonhardt this should probably just be disposed when this is disabled
|
||||
// and then set up again when it is enabled again
|
||||
if (!this._dynamicMessageLayoutData?.enabled) {
|
||||
return;
|
||||
}
|
||||
mutableDisposable.value = dom.scheduleAtNextAnimationFrame(() => {
|
||||
if (!e.scrollTopChanged || e.heightChanged || e.scrollHeightChanged) {
|
||||
return;
|
||||
@@ -593,7 +601,9 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
if (!this.viewModel || !this._dynamicMessageLayoutData?.enabled) {
|
||||
return;
|
||||
}
|
||||
const inputHeight = this.inputPart.layout(this._dynamicMessageLayoutData!.maxHeight, this.container.offsetWidth);
|
||||
|
||||
const width = this.bodyDimension?.width ?? this.container.offsetWidth;
|
||||
const inputHeight = this.inputPart.layout(this._dynamicMessageLayoutData!.maxHeight, width);
|
||||
|
||||
const totalMessages = this.viewModel.getItems();
|
||||
// grab the last N messages
|
||||
@@ -610,10 +620,10 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
inputHeight + listHeight + (totalMessages.length > 2 ? 18 : 0),
|
||||
this._dynamicMessageLayoutData!.maxHeight
|
||||
),
|
||||
this.container.offsetWidth
|
||||
width
|
||||
);
|
||||
|
||||
if (needsRerender) {
|
||||
if (needsRerender || !listHeight) {
|
||||
// TODO: figure out a better place to reveal the last element
|
||||
revealLastElement(this.tree);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
@@ -44,6 +45,7 @@ class InputEditorDecorations extends Disposable {
|
||||
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IChatService private readonly chatService: IChatService,
|
||||
@IChatVariablesService private readonly chatVariablesService: IChatVariablesService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -175,21 +177,22 @@ class InputEditorDecorations extends Disposable {
|
||||
this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandTextDecorationType, []);
|
||||
}
|
||||
|
||||
// const variables = this.chatVariablesService.getVariables();
|
||||
const variables = this.chatVariablesService.getVariables();
|
||||
const variableReg = /(^|\s)@(\w+)(:\d+)?(?=(\s|$))/ig;
|
||||
let match: RegExpMatchArray | null;
|
||||
const varDecorations: IDecorationOptions[] = [];
|
||||
while (match = variableReg.exec(inputValue)) {
|
||||
// const candidate = match[2];
|
||||
// if (Iterable.find(variables, v => v.name === candidate))
|
||||
varDecorations.push({
|
||||
range: {
|
||||
startLineNumber: 1,
|
||||
endLineNumber: 1,
|
||||
startColumn: match.index! + match[1].length + 1,
|
||||
endColumn: match.index! + match[0].length + 1
|
||||
}
|
||||
});
|
||||
const varName = match[2];
|
||||
if (Iterable.find(variables, v => v.name === varName)) {
|
||||
varDecorations.push({
|
||||
range: {
|
||||
startLineNumber: 1,
|
||||
endLineNumber: 1,
|
||||
startColumn: match.index! + match[1].length + 1,
|
||||
endColumn: match.index! + match[0].length + 1
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.widget.inputEditor.setDecorationsByType(decorationDescription, variableTextDecorationType, varDecorations);
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
|
||||
.interactive-session .interactive-input-and-side-toolbar {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -427,6 +427,7 @@
|
||||
.quick-input-widget .interactive-session .interactive-input-and-execute-toolbar {
|
||||
margin: 0;
|
||||
border-radius: 2px;
|
||||
padding: 0 4px 0 6px;
|
||||
}
|
||||
|
||||
.quick-input-widget .interactive-list {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export const CONTEXT_RESPONSE_HAS_PROVIDER_ID = new RawContextKey<boolean>('chatSessionResponseHasProviderId', false, { type: 'boolean', description: localize('interactiveSessionResponseHasProviderId', "True when the provider has assigned an id to this response.") });
|
||||
export const CONTEXT_RESPONSE_VOTE = new RawContextKey<string>('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") });
|
||||
export const CONTEXT_RESPONSE_FILTERED = new RawContextKey<boolean>('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") });
|
||||
export const CONTEXT_CHAT_REQUEST_IN_PROGRESS = new RawContextKey<boolean>('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") });
|
||||
|
||||
export const CONTEXT_RESPONSE = new RawContextKey<boolean>('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") });
|
||||
|
||||
@@ -160,6 +160,7 @@ export class Response implements IResponse {
|
||||
});
|
||||
} else if (isCompleteInteractiveProgressTreeData(responsePart)) {
|
||||
this._responseParts.push(responsePart);
|
||||
this._updateRepr(quiet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover,
|
||||
.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover {
|
||||
animation: none; /* stop the running voice recording animation for showing another codicon to stop */
|
||||
}
|
||||
|
||||
.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover::before,
|
||||
.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover::before {
|
||||
content: "\ead7"; /* use `debug-stop` icon unicode for hovering over running voice recording */
|
||||
}
|
||||
@@ -3,11 +3,12 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/voiceChatActions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { firstOrDefault } from 'vs/base/common/arrays';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -16,7 +17,7 @@ import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/c
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { spinningLoading } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IWorkbenchVoiceRecognitionService } from 'vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService';
|
||||
import { MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
||||
@@ -24,58 +25,197 @@ import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatCo
|
||||
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { isExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { isExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions';
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
|
||||
const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey<boolean>('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone.") });
|
||||
const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey<boolean>('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress.") });
|
||||
const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey<boolean>('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") });
|
||||
const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey<boolean>('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") });
|
||||
|
||||
const CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS = new RawContextKey<boolean>('quickVoiceChatInProgress', false, { type: 'boolean', description: localize('quickVoiceChatInProgress', "True when voice recording from microphone is in progress for quick chat.") });
|
||||
const CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS = new RawContextKey<boolean>('inlineVoiceChatInProgress', false, { type: 'boolean', description: localize('inlineVoiceChatInProgress', "True when voice recording from microphone is in progress for inline chat.") });
|
||||
const CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS = new RawContextKey<boolean>('voiceChatInViewInProgress', false, { type: 'boolean', description: localize('voiceChatInViewInProgress', "True when voice recording from microphone is in progress in the chat view.") });
|
||||
const CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS = new RawContextKey<boolean>('voiceChatInEditorInProgress', false, { type: 'boolean', description: localize('voiceChatInEditorInProgress', "True when voice recording from microphone is in progress in the chat editor.") });
|
||||
|
||||
type VoiceChatSessionContext = 'inline' | 'quick' | 'view' | 'editor';
|
||||
|
||||
interface IVoiceChatSessionController {
|
||||
|
||||
readonly onDidAcceptInput: Event<unknown>;
|
||||
readonly onDidCancelInput: Event<unknown>;
|
||||
|
||||
readonly context: VoiceChatSessionContext;
|
||||
|
||||
focusInput(): void;
|
||||
acceptInput(): void;
|
||||
updateInput(text: string): void;
|
||||
}
|
||||
|
||||
function getController(controller: InlineChatController): IVoiceChatSessionController;
|
||||
function getController(context: unknown): IVoiceChatSessionController | undefined;
|
||||
function getController(context: unknown): IVoiceChatSessionController | undefined {
|
||||
if (context instanceof InlineChatController) {
|
||||
class VoiceChatSessionControllerFactory {
|
||||
|
||||
static create(accessor: ServicesAccessor, context: 'inline'): Promise<IVoiceChatSessionController | undefined>;
|
||||
static create(accessor: ServicesAccessor, context: 'quick'): Promise<IVoiceChatSessionController | undefined>;
|
||||
static create(accessor: ServicesAccessor, context: 'view'): Promise<IVoiceChatSessionController | undefined>;
|
||||
static create(accessor: ServicesAccessor, context: 'focussed'): Promise<IVoiceChatSessionController | undefined>;
|
||||
static async create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focussed'): Promise<IVoiceChatSessionController | undefined> {
|
||||
const chatWidgetService = accessor.get(IChatWidgetService);
|
||||
const chatService = accessor.get(IChatService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const chatContributionService = accessor.get(IChatContributionService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const quickChatService = accessor.get(IQuickChatService);
|
||||
const layoutService = accessor.get(IWorkbenchLayoutService);
|
||||
|
||||
// Currently Focussed Context
|
||||
if (context === 'focussed') {
|
||||
|
||||
// Try with the chat widget service, which currently
|
||||
// only supports the chat view and quick chat
|
||||
// https://github.com/microsoft/vscode/issues/191191
|
||||
const chatInput = chatWidgetService.lastFocusedWidget;
|
||||
if (chatInput?.hasInputFocus()) {
|
||||
// Unfortunately there does not seem to be a better way
|
||||
// to figure out if the chat widget is in a part or picker
|
||||
if (
|
||||
layoutService.hasFocus(Parts.SIDEBAR_PART) ||
|
||||
layoutService.hasFocus(Parts.PANEL_PART) ||
|
||||
layoutService.hasFocus(Parts.AUXILIARYBAR_PART)
|
||||
) {
|
||||
return VoiceChatSessionControllerFactory.doCreateForChatView(chatInput, viewsService, chatContributionService);
|
||||
}
|
||||
|
||||
if (layoutService.hasFocus(Parts.EDITOR_PART)) {
|
||||
return VoiceChatSessionControllerFactory.doCreateForChatEditor(chatInput, viewsService, chatContributionService);
|
||||
}
|
||||
|
||||
return VoiceChatSessionControllerFactory.doCreateForQuickChat(chatInput, quickChatService);
|
||||
}
|
||||
|
||||
// Try with the inline chat
|
||||
const activeCodeEditor = getCodeEditor(editorService.activeTextEditorControl);
|
||||
if (activeCodeEditor) {
|
||||
const inlineChat = InlineChatController.get(activeCodeEditor);
|
||||
if (inlineChat?.hasFocus()) {
|
||||
return VoiceChatSessionControllerFactory.doCreateForInlineChat(inlineChat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// View Chat
|
||||
if (context === 'view') {
|
||||
const provider = firstOrDefault(chatService.getProviderInfos());
|
||||
if (provider) {
|
||||
const chatView = await chatWidgetService.revealViewForProvider(provider.id);
|
||||
if (chatView) {
|
||||
return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService, chatContributionService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inline Chat
|
||||
if (context === 'inline') {
|
||||
const activeCodeEditor = getCodeEditor(editorService.activeTextEditorControl);
|
||||
if (activeCodeEditor) {
|
||||
const inlineChat = InlineChatController.get(activeCodeEditor);
|
||||
if (inlineChat) {
|
||||
return VoiceChatSessionControllerFactory.doCreateForInlineChat(inlineChat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Quick Chat
|
||||
if (context === 'quick') {
|
||||
quickChatService.open();
|
||||
|
||||
const quickChat = chatWidgetService.lastFocusedWidget;
|
||||
if (quickChat) {
|
||||
return VoiceChatSessionControllerFactory.doCreateForQuickChat(quickChat, quickChatService);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController {
|
||||
return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService, chatContributionService);
|
||||
}
|
||||
|
||||
private static doCreateForChatEditor(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController {
|
||||
return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('editor', chatView, viewsService, chatContributionService);
|
||||
}
|
||||
|
||||
private static doCreateForChatViewOrEditor(context: 'view' | 'editor', chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController {
|
||||
return {
|
||||
onDidAcceptInput: context.onDidAcceptInput,
|
||||
focusInput: () => context.focus(),
|
||||
acceptInput: () => context.acceptInput(),
|
||||
updateInput: text => context.updateInput(text)
|
||||
context,
|
||||
onDidAcceptInput: chatView.onDidAcceptInput,
|
||||
// TODO@bpasero cancellation needs to work better for chat editors that are not view bound
|
||||
onDidCancelInput: Event.filter(viewsService.onDidChangeViewVisibility, e => e.id === chatContributionService.getViewIdForProvider(chatView.providerId)),
|
||||
focusInput: () => chatView.focusInput(),
|
||||
acceptInput: () => chatView.acceptInput(),
|
||||
updateInput: text => chatView.updateInput(text)
|
||||
};
|
||||
}
|
||||
|
||||
if (isExecuteActionContext(context)) {
|
||||
return context.widget;
|
||||
private static doCreateForQuickChat(quickChat: IChatWidget, quickChatService: IQuickChatService): IVoiceChatSessionController {
|
||||
return {
|
||||
context: 'quick',
|
||||
onDidAcceptInput: quickChat.onDidAcceptInput,
|
||||
onDidCancelInput: quickChatService.onDidClose,
|
||||
focusInput: () => quickChat.focusInput(),
|
||||
acceptInput: () => quickChat.acceptInput(),
|
||||
updateInput: text => quickChat.updateInput(text)
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
private static doCreateForInlineChat(inlineChat: InlineChatController,): IVoiceChatSessionController {
|
||||
const inlineChatSession = inlineChat.run();
|
||||
|
||||
return {
|
||||
context: 'inline',
|
||||
onDidAcceptInput: inlineChat.onDidAcceptInput,
|
||||
onDidCancelInput: Event.any(
|
||||
inlineChat.onDidCancelInput,
|
||||
Event.fromPromise(inlineChatSession)
|
||||
),
|
||||
focusInput: () => inlineChat.focus(),
|
||||
acceptInput: () => inlineChat.acceptInput(),
|
||||
updateInput: text => inlineChat.updateInput(text)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class VoiceChatSession {
|
||||
interface ActiveVoiceChatSession {
|
||||
readonly controller: IVoiceChatSessionController;
|
||||
readonly disposables: DisposableStore;
|
||||
}
|
||||
|
||||
private static instance: VoiceChatSession | undefined = undefined;
|
||||
static getInstance(instantiationService: IInstantiationService): VoiceChatSession {
|
||||
if (!VoiceChatSession.instance) {
|
||||
VoiceChatSession.instance = instantiationService.createInstance(VoiceChatSession);
|
||||
class VoiceChatSessions {
|
||||
|
||||
private static instance: VoiceChatSessions | undefined = undefined;
|
||||
static getInstance(instantiationService: IInstantiationService): VoiceChatSessions {
|
||||
if (!VoiceChatSessions.instance) {
|
||||
VoiceChatSessions.instance = instantiationService.createInstance(VoiceChatSessions);
|
||||
}
|
||||
|
||||
return VoiceChatSession.instance;
|
||||
return VoiceChatSessions.instance;
|
||||
}
|
||||
|
||||
private voiceChatInProgressKey = CONTEXT_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService);
|
||||
private voiceChatGettingReadyKey = CONTEXT_VOICE_CHAT_GETTING_READY.bindTo(this.contextKeyService);
|
||||
|
||||
private currentVoiceChatSession: DisposableStore | undefined = undefined;
|
||||
private quickVoiceChatInProgressKey = CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService);
|
||||
private inlineVoiceChatInProgressKey = CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService);
|
||||
private voiceChatInViewInProgressKey = CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.bindTo(this.contextKeyService);
|
||||
private voiceChatInEditorInProgressKey = CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.bindTo(this.contextKeyService);
|
||||
|
||||
private currentVoiceChatSession: ActiveVoiceChatSession | undefined = undefined;
|
||||
private voiceChatSessionIds = 0;
|
||||
|
||||
constructor(
|
||||
@@ -83,33 +223,60 @@ class VoiceChatSession {
|
||||
@IWorkbenchVoiceRecognitionService private readonly voiceRecognitionService: IWorkbenchVoiceRecognitionService
|
||||
) { }
|
||||
|
||||
async start(context: IVoiceChatSessionController): Promise<IDisposable> {
|
||||
async start(controller: IVoiceChatSessionController): Promise<void> {
|
||||
this.stop();
|
||||
|
||||
this.voiceChatGettingReadyKey.set(true);
|
||||
this.currentVoiceChatSession = new DisposableStore();
|
||||
const voiceChatSessionId = ++this.voiceChatSessionIds;
|
||||
this.currentVoiceChatSession = {
|
||||
controller,
|
||||
disposables: new DisposableStore()
|
||||
};
|
||||
|
||||
const cts = new CancellationTokenSource();
|
||||
this.currentVoiceChatSession.add(toDisposable(() => cts.dispose(true)));
|
||||
this.currentVoiceChatSession.disposables.add(toDisposable(() => cts.dispose(true)));
|
||||
|
||||
context.focusInput();
|
||||
this.currentVoiceChatSession.disposables.add(controller.onDidAcceptInput(() => this.stop(voiceChatSessionId, controller.context)));
|
||||
this.currentVoiceChatSession.disposables.add(controller.onDidCancelInput(() => this.stop(voiceChatSessionId, controller.context)));
|
||||
|
||||
controller.updateInput('');
|
||||
controller.focusInput();
|
||||
|
||||
this.voiceChatGettingReadyKey.set(true);
|
||||
|
||||
const onDidTranscribe = await this.voiceRecognitionService.transcribe(cts.token, {
|
||||
onDidCancel: () => this.stop(voiceChatSessionId)
|
||||
onDidCancel: () => this.stop(voiceChatSessionId, controller.context)
|
||||
});
|
||||
if (cts.token.isCancellationRequested) {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
const voiceChatSessionId = ++this.voiceChatSessionIds;
|
||||
if (cts.token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.voiceChatGettingReadyKey.set(false);
|
||||
this.voiceChatInProgressKey.set(true);
|
||||
|
||||
switch (controller.context) {
|
||||
case 'inline':
|
||||
this.inlineVoiceChatInProgressKey.set(true);
|
||||
break;
|
||||
case 'quick':
|
||||
this.quickVoiceChatInProgressKey.set(true);
|
||||
break;
|
||||
case 'view':
|
||||
this.voiceChatInViewInProgressKey.set(true);
|
||||
break;
|
||||
case 'editor':
|
||||
this.voiceChatInEditorInProgressKey.set(true);
|
||||
break;
|
||||
}
|
||||
|
||||
this.registerTranscriptionListener(this.currentVoiceChatSession, onDidTranscribe);
|
||||
}
|
||||
|
||||
private registerTranscriptionListener(session: ActiveVoiceChatSession, onDidTranscribe: Event<string>) {
|
||||
let lastText: string | undefined = undefined;
|
||||
let lastTextSimilarCount = 0;
|
||||
|
||||
this.currentVoiceChatSession.add(onDidTranscribe(text => {
|
||||
session.disposables.add(onDidTranscribe(text => {
|
||||
if (!text && lastText) {
|
||||
text = lastText;
|
||||
}
|
||||
@@ -123,17 +290,12 @@ class VoiceChatSession {
|
||||
}
|
||||
|
||||
if (lastTextSimilarCount >= 2) {
|
||||
context.acceptInput();
|
||||
session.controller.acceptInput();
|
||||
} else {
|
||||
context.updateInput(text);
|
||||
session.controller.updateInput(text);
|
||||
}
|
||||
|
||||
}
|
||||
}));
|
||||
|
||||
this.currentVoiceChatSession.add(context.onDidAcceptInput(() => this.stop(voiceChatSessionId)));
|
||||
|
||||
return toDisposable(() => this.stop(voiceChatSessionId));
|
||||
}
|
||||
|
||||
private isSimilarTranscription(textA: string, textB: string): boolean {
|
||||
@@ -150,20 +312,36 @@ class VoiceChatSession {
|
||||
);
|
||||
}
|
||||
|
||||
stop(voiceChatSessionId = this.voiceChatSessionIds): void {
|
||||
if (!this.currentVoiceChatSession) {
|
||||
stop(voiceChatSessionId = this.voiceChatSessionIds, context?: VoiceChatSessionContext): void {
|
||||
if (
|
||||
!this.currentVoiceChatSession ||
|
||||
this.voiceChatSessionIds !== voiceChatSessionId ||
|
||||
(context && this.currentVoiceChatSession.controller.context !== context)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.voiceChatSessionIds !== voiceChatSessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentVoiceChatSession.dispose();
|
||||
this.currentVoiceChatSession.disposables.dispose();
|
||||
this.currentVoiceChatSession = undefined;
|
||||
|
||||
this.voiceChatGettingReadyKey.set(false);
|
||||
this.voiceChatInProgressKey.set(false);
|
||||
|
||||
this.quickVoiceChatInProgressKey.set(false);
|
||||
this.inlineVoiceChatInProgressKey.set(false);
|
||||
this.voiceChatInViewInProgressKey.set(false);
|
||||
this.voiceChatInEditorInProgressKey.set(false);
|
||||
}
|
||||
|
||||
accept(voiceChatSessionId = this.voiceChatSessionIds): void {
|
||||
if (
|
||||
!this.currentVoiceChatSession ||
|
||||
this.voiceChatSessionIds !== voiceChatSessionId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentVoiceChatSession.controller.acceptInput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,16 +363,11 @@ class VoiceChatInChatViewAction extends Action2 {
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const chatWidgetService = accessor.get(IChatWidgetService);
|
||||
const chatService = accessor.get(IChatService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
const provider = firstOrDefault(chatService.getProviderInfos());
|
||||
if (provider) {
|
||||
const controller = await chatWidgetService.revealViewForProvider(provider.id);
|
||||
if (controller) {
|
||||
VoiceChatSession.getInstance(instantiationService).start(controller);
|
||||
}
|
||||
const controller = await VoiceChatSessionControllerFactory.create(accessor, 'view');
|
||||
if (controller) {
|
||||
VoiceChatSessions.getInstance(instantiationService).start(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,23 +390,12 @@ class InlineVoiceChatAction extends Action2 {
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
const activeCodeEditor = getCodeEditor(editorService.activeTextEditorControl);
|
||||
if (!activeCodeEditor) {
|
||||
return;
|
||||
const controller = await VoiceChatSessionControllerFactory.create(accessor, 'inline');
|
||||
if (controller) {
|
||||
VoiceChatSessions.getInstance(instantiationService).start(controller);
|
||||
}
|
||||
|
||||
const controller = InlineChatController.get(activeCodeEditor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inlineChatSession = controller.run();
|
||||
|
||||
const disposable = await VoiceChatSession.getInstance(instantiationService).start(getController(controller));
|
||||
inlineChatSession.finally(() => disposable.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,16 +417,11 @@ class QuickVoiceChatAction extends Action2 {
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const quickChatService = accessor.get(IQuickChatService);
|
||||
const chatWidgetService = accessor.get(IChatWidgetService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
quickChatService.open();
|
||||
|
||||
const controller = chatWidgetService.lastFocusedWidget;
|
||||
const controller = await VoiceChatSessionControllerFactory.create(accessor, 'quick');
|
||||
if (controller) {
|
||||
const disposable = await VoiceChatSession.getInstance(instantiationService).start(controller);
|
||||
Event.once(quickChatService.onDidClose)(() => disposable.dispose());
|
||||
VoiceChatSessions.getInstance(instantiationService).start(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,16 +437,16 @@ class StartVoiceChatAction extends Action2 {
|
||||
value: localize('workbench.action.chat.startVoiceChat', "Start Voice Chat"),
|
||||
original: 'Start Voice Chat'
|
||||
},
|
||||
icon: Codicon.record,
|
||||
icon: Codicon.mic,
|
||||
precondition: CONTEXT_VOICE_CHAT_GETTING_READY.negate(),
|
||||
menu: [{
|
||||
id: MenuId.ChatExecute,
|
||||
when: CONTEXT_VOICE_CHAT_IN_PROGRESS.negate(),
|
||||
when: ContextKeyExpr.and(CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.negate(), CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.negate(), CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.negate()),
|
||||
group: 'navigation',
|
||||
order: -1
|
||||
}, {
|
||||
id: MENU_INLINE_CHAT_WIDGET,
|
||||
when: CONTEXT_VOICE_CHAT_IN_PROGRESS.negate(),
|
||||
when: CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate(),
|
||||
group: 'main',
|
||||
order: -1
|
||||
}]
|
||||
@@ -297,45 +454,27 @@ class StartVoiceChatAction extends Action2 {
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, context: unknown): Promise<void> {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const chatWidgetService = accessor.get(IChatWidgetService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
let controller = getController(context);
|
||||
if (!controller) {
|
||||
|
||||
// Without a controller, this action potentially executed from
|
||||
// a global keybinding, and thus we have to find the chat
|
||||
// input that is currently focussed, or have a fallback
|
||||
|
||||
// 1.) a chat input widget has focus
|
||||
if (chatWidgetService.lastFocusedWidget?.hasInputFocus()) {
|
||||
controller = chatWidgetService.lastFocusedWidget;
|
||||
}
|
||||
|
||||
// 2.) a inline chat input widget has focus
|
||||
if (!controller) {
|
||||
const activeCodeEditor = getCodeEditor(editorService.activeTextEditorControl);
|
||||
if (activeCodeEditor) {
|
||||
const chatInput = InlineChatController.get(activeCodeEditor);
|
||||
if (chatInput?.hasFocus()) {
|
||||
controller = getController(chatInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3.) open a quick chat view
|
||||
if (!controller) {
|
||||
return commandService.executeCommand(QuickVoiceChatAction.ID);
|
||||
}
|
||||
if (isExecuteActionContext(context)) {
|
||||
// if we already get a context when the action is executed
|
||||
// from a toolbar within the chat widget, then make sure
|
||||
// to move focus into the input field so that the controller
|
||||
// is properly retrieved
|
||||
// TODO@bpasero this will actually not work if the button
|
||||
// is clicked from the inline editor while focus is in a
|
||||
// chat input field in a view or picker
|
||||
context.widget.focusInput();
|
||||
}
|
||||
|
||||
if (!controller) {
|
||||
return;
|
||||
const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focussed');
|
||||
if (controller) {
|
||||
VoiceChatSessions.getInstance(instantiationService).start(controller);
|
||||
} else {
|
||||
// fallback to Quick Voice Chat command
|
||||
commandService.executeCommand(QuickVoiceChatAction.ID);
|
||||
}
|
||||
|
||||
VoiceChatSession.getInstance(instantiationService).start(controller);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,16 +491,141 @@ class StopVoiceChatAction extends Action2 {
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
f1: true,
|
||||
precondition: CONTEXT_VOICE_CHAT_IN_PROGRESS,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib + 100,
|
||||
when: CONTEXT_VOICE_CHAT_IN_PROGRESS,
|
||||
primary: KeyCode.Escape
|
||||
},
|
||||
precondition: CONTEXT_VOICE_CHAT_IN_PROGRESS
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop();
|
||||
}
|
||||
}
|
||||
|
||||
class StopVoiceChatInChatViewAction extends Action2 {
|
||||
|
||||
static readonly ID = 'workbench.action.chat.stopVoiceChatInChatView';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: StopVoiceChatInChatViewAction.ID,
|
||||
title: {
|
||||
value: localize('workbench.action.chat.stopVoiceChatInChatView.label', "Stop Voice Chat (Chat View)"),
|
||||
original: 'Stop Voice Chat (Chat View)'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib + 100,
|
||||
when: CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS,
|
||||
primary: KeyCode.Escape
|
||||
},
|
||||
precondition: CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS,
|
||||
icon: spinningLoading,
|
||||
menu: [{
|
||||
id: MenuId.ChatExecute,
|
||||
when: CONTEXT_VOICE_CHAT_IN_PROGRESS,
|
||||
when: CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS,
|
||||
group: 'navigation',
|
||||
order: -1
|
||||
}, {
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'view');
|
||||
}
|
||||
}
|
||||
|
||||
class StopVoiceChatInChatEditorAction extends Action2 {
|
||||
|
||||
static readonly ID = 'workbench.action.chat.stopVoiceChatInChatEditor';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: StopVoiceChatInChatEditorAction.ID,
|
||||
title: {
|
||||
value: localize('workbench.action.chat.stopVoiceChatInChatEditor.label', "Stop Voice Chat (Chat Editor)"),
|
||||
original: 'Stop Voice Chat (Chat Editor)'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib + 100,
|
||||
when: CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS,
|
||||
primary: KeyCode.Escape
|
||||
},
|
||||
precondition: CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS,
|
||||
icon: spinningLoading,
|
||||
menu: [{
|
||||
id: MenuId.ChatExecute,
|
||||
when: CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS,
|
||||
group: 'navigation',
|
||||
order: -1
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'editor');
|
||||
}
|
||||
}
|
||||
|
||||
class StopQuickVoiceChatAction extends Action2 {
|
||||
|
||||
static readonly ID = 'workbench.action.chat.stopQuickVoiceChat';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: StopQuickVoiceChatAction.ID,
|
||||
title: {
|
||||
value: localize('workbench.action.chat.stopQuickVoiceChat.label', "Stop Voice Chat (Quick Chat)"),
|
||||
original: 'Stop Voice Chat (Quick Chat)'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib + 100,
|
||||
when: CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS,
|
||||
primary: KeyCode.Escape
|
||||
},
|
||||
precondition: CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS,
|
||||
icon: spinningLoading,
|
||||
menu: [{
|
||||
id: MenuId.ChatExecute,
|
||||
when: CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS,
|
||||
group: 'navigation',
|
||||
order: -1
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'quick');
|
||||
}
|
||||
}
|
||||
|
||||
class StopInlineVoiceChatAction extends Action2 {
|
||||
|
||||
static readonly ID = 'workbench.action.chat.stopInlineVoiceChat';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: StopInlineVoiceChatAction.ID,
|
||||
title: {
|
||||
value: localize('workbench.action.chat.stopInlineVoiceChat.label', "Stop Voice Chat (Inline Editor)"),
|
||||
original: 'Stop Voice Chat (Inline Editor)'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib + 100,
|
||||
when: CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS,
|
||||
primary: KeyCode.Escape
|
||||
},
|
||||
precondition: CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS,
|
||||
icon: spinningLoading,
|
||||
menu: [{
|
||||
id: MENU_INLINE_CHAT_WIDGET,
|
||||
when: CONTEXT_VOICE_CHAT_IN_PROGRESS,
|
||||
when: CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS,
|
||||
group: 'main',
|
||||
order: -1
|
||||
}]
|
||||
@@ -369,7 +633,29 @@ class StopVoiceChatAction extends Action2 {
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
VoiceChatSession.getInstance(accessor.get(IInstantiationService)).stop();
|
||||
VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'inline');
|
||||
}
|
||||
}
|
||||
|
||||
class StopVoiceChatAndSubmitAction extends Action2 {
|
||||
|
||||
static readonly ID = 'workbench.action.chat.stopVoiceChatAndSubmit';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: StopVoiceChatAndSubmitAction.ID,
|
||||
title: {
|
||||
value: localize('workbench.action.chat.stopAndAcceptVoiceChat.label', "Stop Voice Chat and Submit"),
|
||||
original: 'Stop Voice Chat and Submit'
|
||||
},
|
||||
category: CHAT_CATEGORY,
|
||||
f1: true,
|
||||
precondition: CONTEXT_VOICE_CHAT_IN_PROGRESS
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).accept();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,5 +667,11 @@ export function registerVoiceChatActions() {
|
||||
|
||||
registerAction2(StartVoiceChatAction);
|
||||
registerAction2(StopVoiceChatAction);
|
||||
registerAction2(StopVoiceChatAndSubmitAction);
|
||||
|
||||
registerAction2(StopVoiceChatInChatViewAction);
|
||||
registerAction2(StopVoiceChatInChatEditorAction);
|
||||
registerAction2(StopQuickVoiceChatAction);
|
||||
registerAction2(StopInlineVoiceChatAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super(editor, { keepEditorSelection: true });
|
||||
super(editor, { keepEditorSelection: true, isAccessible: true });
|
||||
this._contextKeyService = contextKeyService.createScoped(this.domNode);
|
||||
|
||||
this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection(
|
||||
|
||||
@@ -104,7 +104,8 @@ export class SimpleCommentEditor extends CodeEditorWidget {
|
||||
enabled: false
|
||||
},
|
||||
autoClosingBrackets: configurationService.getValue('editor.autoClosingBrackets'),
|
||||
quickSuggestions: false
|
||||
quickSuggestions: false,
|
||||
accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,10 +855,12 @@ export class ExtensionEditor extends EditorPane {
|
||||
extensionPackReadme.style.maxWidth = '882px';
|
||||
|
||||
const extensionPack = append(extensionPackReadme, $('div', { class: 'extension-pack' }));
|
||||
if (manifest.extensionPack!.length < 3) {
|
||||
if (manifest.extensionPack!.length <= 3) {
|
||||
extensionPackReadme.classList.add('one-row');
|
||||
} else if (manifest.extensionPack!.length < 5) {
|
||||
} else if (manifest.extensionPack!.length <= 6) {
|
||||
extensionPackReadme.classList.add('two-rows');
|
||||
} else if (manifest.extensionPack!.length <= 9) {
|
||||
extensionPackReadme.classList.add('three-rows');
|
||||
} else {
|
||||
extensionPackReadme.classList.add('more-rows');
|
||||
}
|
||||
|
||||
@@ -517,6 +517,10 @@
|
||||
height: 224px;
|
||||
}
|
||||
|
||||
.extension-editor > .body > .content > .details > .readme-container > .extension-pack-readme.three-rows > .extension-pack {
|
||||
height: 306px;
|
||||
}
|
||||
|
||||
.extension-editor > .body > .content > .details > .readme-container > .extension-pack-readme.more-rows > .extension-pack {
|
||||
height: 326px;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ export class InlineChatController implements IEditorContribution {
|
||||
private _messages = this._store.add(new Emitter<Message>());
|
||||
|
||||
readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store);
|
||||
readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store);
|
||||
|
||||
private readonly _sessionStore: DisposableStore = this._store.add(new DisposableStore());
|
||||
private readonly _stashedSession: MutableDisposable<StashedSession> = this._store.add(new MutableDisposable());
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
|
||||
.interactive-editor .input-cell-container .monaco-editor-background,
|
||||
.interactive-editor .input-cell-container .margin-view-overlays {
|
||||
background-color: var(--vscode-notebook-cellEditorBackground, --vscode-editor-background);
|
||||
background-color: var(--vscode-notebook-cellEditorBackground, var(--vscode-editor-background));
|
||||
}
|
||||
|
||||
@@ -61,5 +61,7 @@ export const TEXT_BASED_MIMETYPES = [
|
||||
'application/x.notebook.stream',
|
||||
'application/vnd.code.notebook.stderr',
|
||||
'application/x.notebook.stderr',
|
||||
'text/plain'
|
||||
'text/plain',
|
||||
'text/markdown',
|
||||
'application/json'
|
||||
];
|
||||
|
||||
@@ -17,13 +17,13 @@ import { ICellOutputViewModel, ICellViewModel, INotebookEditor, getNotebookEdito
|
||||
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
|
||||
|
||||
export const COPY_OUTPUT_COMMAND_ID = 'notebook.cellOutput.copyToClipboard';
|
||||
export const COPY_OUTPUT_COMMAND_ID = 'notebook.cellOutput.copy';
|
||||
|
||||
registerAction2(class CopyCellOutputAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: COPY_OUTPUT_COMMAND_ID,
|
||||
title: localize('notebookActions.copyOutput', "Copy Output to Clipboard"),
|
||||
title: localize('notebookActions.copyOutput', "Copy Output"),
|
||||
menu: {
|
||||
id: MenuId.NotebookOutputToolbar,
|
||||
when: NOTEBOOK_CELL_HAS_OUTPUTS
|
||||
@@ -55,7 +55,7 @@ registerAction2(class CopyCellOutputAction extends Action2 {
|
||||
const mimeType = outputViewModel.pickedMimeType?.mimeType;
|
||||
|
||||
if (mimeType?.startsWith('image/')) {
|
||||
const focusOptions = { skipReveal: true, outputId: outputViewModel.model.outputId };
|
||||
const focusOptions = { skipReveal: true, outputId: outputViewModel.model.outputId, altOutputId: outputViewModel.model.alternativeOutputId };
|
||||
await notebookEditor.focusNotebookCell(outputViewModel.cellViewModel as ICellViewModel, 'output', focusOptions);
|
||||
notebookEditor.copyOutputImage(outputViewModel);
|
||||
} else {
|
||||
|
||||
@@ -152,6 +152,7 @@ export interface IFocusNotebookCellOptions {
|
||||
readonly focusEditorLine?: number;
|
||||
readonly minimalScrolling?: boolean;
|
||||
readonly outputId?: string;
|
||||
readonly altOutputId?: string;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -2350,7 +2350,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
}
|
||||
|
||||
const focusElementId = options?.outputId ?? cell.id;
|
||||
this._webview.focusOutput(focusElementId, this._webviewFocused);
|
||||
this._webview.focusOutput(focusElementId, options?.altOutputId, this._webviewFocused);
|
||||
|
||||
cell.updateEditState(CellEditState.Preview, 'focusNotebookCell');
|
||||
cell.focusMode = CellFocusMode.Output;
|
||||
|
||||
@@ -1548,7 +1548,8 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
|
||||
async copyImage(output: ICellOutputViewModel): Promise<void> {
|
||||
this._sendMessageToWebview({
|
||||
type: 'copyImage',
|
||||
outputId: output.model.outputId
|
||||
outputId: output.model.outputId,
|
||||
altOutputId: output.model.alternativeOutputId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1608,7 +1609,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
|
||||
this.webview?.focus();
|
||||
}
|
||||
|
||||
focusOutput(cellOrOutputId: string, viewFocused: boolean) {
|
||||
focusOutput(cellOrOutputId: string, alternateId: string | undefined, viewFocused: boolean) {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
@@ -1620,6 +1621,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
|
||||
this._sendMessageToWebview({
|
||||
type: 'focus-output',
|
||||
cellOrOutputId: cellOrOutputId,
|
||||
alternateId: alternateId
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -269,11 +269,13 @@ export interface IShowOutputMessage {
|
||||
export interface ICopyImageMessage {
|
||||
readonly type: 'copyImage';
|
||||
readonly outputId: string;
|
||||
readonly altOutputId: string;
|
||||
}
|
||||
|
||||
export interface IFocusOutputMessage {
|
||||
readonly type: 'focus-output';
|
||||
readonly cellOrOutputId: string;
|
||||
readonly alternateId?: string;
|
||||
}
|
||||
|
||||
export interface IAckOutputHeight {
|
||||
|
||||
@@ -474,8 +474,9 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
});
|
||||
};
|
||||
|
||||
function focusFirstFocusableOrContainerInOutput(cellOrOutputId: string) {
|
||||
const cellOutputContainer = document.getElementById(cellOrOutputId);
|
||||
function focusFirstFocusableOrContainerInOutput(cellOrOutputId: string, alternateId?: string) {
|
||||
const cellOutputContainer = document.getElementById(cellOrOutputId) ??
|
||||
alternateId ? document.getElementById(alternateId!) : undefined;
|
||||
if (cellOutputContainer) {
|
||||
if (cellOutputContainer.contains(document.activeElement)) {
|
||||
return;
|
||||
@@ -1362,17 +1363,18 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
});
|
||||
};
|
||||
|
||||
const copyOutputImage = async (outputId: string, retries = 5) => {
|
||||
const copyOutputImage = async (outputId: string, altOutputId: string, retries = 5) => {
|
||||
if (!document.hasFocus() && retries > 0) {
|
||||
// copyImage can be called from outside of the webview, which means this function may be running whilst the webview is gaining focus.
|
||||
// Since navigator.clipboard.write requires the document to be focused, we need to wait for focus.
|
||||
// We cannot use a listener, as there is a high chance the focus is gained during the setup of the listener resulting in us missing it.
|
||||
setTimeout(() => { copyOutputImage(outputId, retries - 1); }, 20);
|
||||
setTimeout(() => { copyOutputImage(outputId, altOutputId, retries - 1); }, 20);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const image = document.getElementById(outputId)?.querySelector('img');
|
||||
const image = document.getElementById(outputId)?.querySelector('img')
|
||||
?? document.getElementById(altOutputId)?.querySelector('img');
|
||||
if (image) {
|
||||
await navigator.clipboard.write([new ClipboardItem({
|
||||
'image/png': new Promise((resolve) => {
|
||||
@@ -1500,9 +1502,7 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
break;
|
||||
}
|
||||
case 'copyImage': {
|
||||
|
||||
await copyOutputImage(event.data.outputId);
|
||||
|
||||
await copyOutputImage(event.data.outputId, event.data.altOutputId);
|
||||
break;
|
||||
}
|
||||
case 'ack-dimension': {
|
||||
@@ -1524,7 +1524,7 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
break;
|
||||
}
|
||||
case 'focus-output':
|
||||
focusFirstFocusableOrContainerInOutput(event.data.cellOrOutputId);
|
||||
focusFirstFocusableOrContainerInOutput(event.data.cellOrOutputId, event.data.alternateId);
|
||||
break;
|
||||
case 'decorations': {
|
||||
let outputContainer = document.getElementById(event.data.cellId);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { mock } from 'vs/base/test/common/mock';
|
||||
@@ -19,7 +20,7 @@ import { createNotebookCellList, setupInstantiationService, withTestNotebook } f
|
||||
import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline';
|
||||
|
||||
|
||||
suite('NotebookEditorStickyScroll', () => {
|
||||
(isWeb ? suite.skip : suite)('NotebookEditorStickyScroll', () => {
|
||||
|
||||
let disposables: DisposableStore;
|
||||
let instantiationService: TestInstantiationService;
|
||||
@@ -92,7 +93,6 @@ suite('NotebookEditorStickyScroll', () => {
|
||||
|
||||
const notebookOutlineEntries = getOutline(editor).entries;
|
||||
const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries);
|
||||
|
||||
await assertSnapshot(resultingMap);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -789,7 +789,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
||||
[CONFIGURATION_KEY_HOST_NAME]: {
|
||||
description: localize('remoteTunnelAccess.machineName', "The name under which the remote tunnel access is registered. If not set, the host name is used."),
|
||||
type: 'string',
|
||||
scope: ConfigurationScope.MACHINE,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
pattern: '^(\\w[\\w-]*)?$',
|
||||
patternErrorMessage: localize('remoteTunnelAccess.machineNameRegex', "The name must only consist of letters, numbers, underscore and dash. It must not start with a dash."),
|
||||
maxLength: 20,
|
||||
@@ -798,7 +798,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
||||
[CONFIGURATION_KEY_PREVENT_SLEEP]: {
|
||||
description: localize('remoteTunnelAccess.preventSleep', "Prevent the computer from sleeping when remote tunnel access is turned on."),
|
||||
type: 'boolean',
|
||||
scope: ConfigurationScope.MACHINE,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
default: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { basenameOrAuthority, dirname } from 'vs/base/common/resources';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
@@ -15,8 +15,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { WorkbenchCompressibleObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService';
|
||||
import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Picks } from 'vs/platform/quickinput/browser/pickerQuickAccess';
|
||||
import { DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { IKeyMods, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { DefaultQuickAccessFilterValue, IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { IKeyMods, IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
@@ -75,6 +75,19 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
|
||||
this.searchModel = this._instantiationService.createInstance(SearchModel);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.searchModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
override provide(picker: IQuickPick<IPickerQuickAccessItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(super.provide(picker, token, runOptions));
|
||||
disposables.add(picker.onDidHide(() => this.searchModel.searchResult.toggleHighlights(false)));
|
||||
disposables.add(picker.onDidAccept(() => this.searchModel.searchResult.toggleHighlights(false)));
|
||||
return disposables;
|
||||
}
|
||||
|
||||
private get configuration() {
|
||||
const editorConfig = this._configurationService.getValue<IWorkbenchEditorConfiguration>().workbench?.editor;
|
||||
const searchConfig = this._configurationService.getValue<IWorkbenchSearchConfiguration>().search;
|
||||
@@ -247,6 +260,9 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
|
||||
}
|
||||
const matches = allMatches.syncResults;
|
||||
const syncResult = this._getPicksFromMatches(matches, MAX_FILES_SHOWN);
|
||||
if (syncResult.length > 0) {
|
||||
this.searchModel.searchResult.toggleHighlights(true);
|
||||
}
|
||||
|
||||
if (matches.length >= MAX_FILES_SHOWN) {
|
||||
return syncResult;
|
||||
@@ -254,9 +270,14 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider<IPickerQuic
|
||||
|
||||
return {
|
||||
picks: syncResult,
|
||||
additionalPicks: allMatches.asyncResults.then((asyncResults) => {
|
||||
return this._getPicksFromMatches(asyncResults, MAX_FILES_SHOWN - matches.length);
|
||||
})
|
||||
additionalPicks: allMatches.asyncResults
|
||||
.then(asyncResults => this._getPicksFromMatches(asyncResults, MAX_FILES_SHOWN - matches.length))
|
||||
.then(picks => {
|
||||
if (picks.length > 0) {
|
||||
this.searchModel.searchResult.toggleHighlights(true);
|
||||
}
|
||||
return picks;
|
||||
})
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1894,7 +1894,6 @@ export class SearchView extends ViewPane {
|
||||
pinned,
|
||||
selection,
|
||||
revealIfVisible: true,
|
||||
indexedCellOptions: element instanceof MatchInNotebook ? { index: element.cellIndex, selection: element.range() } : undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -51,7 +51,7 @@ if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then
|
||||
IFS=':' read -rA ADDR <<< "$VSCODE_ENV_PREPEND"
|
||||
for ITEM in "${ADDR[@]}"; do
|
||||
VARNAME="$(echo ${ITEM%%=*})"
|
||||
export $VARNAME="$(echo -e {ITEM#*=})${(P)VARNAME}"
|
||||
export $VARNAME="$(echo -e ${ITEM#*=})${(P)VARNAME}"
|
||||
done
|
||||
unset VSCODE_ENV_PREPEND
|
||||
fi
|
||||
@@ -59,7 +59,7 @@ if [ -n "${VSCODE_ENV_APPEND:-}" ]; then
|
||||
IFS=':' read -rA ADDR <<< "$VSCODE_ENV_APPEND"
|
||||
for ITEM in "${ADDR[@]}"; do
|
||||
VARNAME="$(echo ${ITEM%%=*})"
|
||||
export $VARNAME="${(P)VARNAME}$(echo -e {ITEM#*=})"
|
||||
export $VARNAME="${(P)VARNAME}$(echo -e ${ITEM#*=})"
|
||||
done
|
||||
unset VSCODE_ENV_APPEND
|
||||
fi
|
||||
|
||||
@@ -590,6 +590,7 @@ export interface ITerminalInstance {
|
||||
onDidBlur: Event<ITerminalInstance>;
|
||||
onDidInputData: Event<ITerminalInstance>;
|
||||
onDidChangeSelection: Event<ITerminalInstance>;
|
||||
onDidRunText: Event<void>;
|
||||
|
||||
/**
|
||||
* An event that fires when a terminal is dropped on this instance via drag and drop.
|
||||
|
||||
@@ -322,6 +322,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
readonly onRequestAddInstanceToGroup = this._onRequestAddInstanceToGroup.event;
|
||||
private readonly _onDidChangeHasChildProcesses = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event;
|
||||
private readonly _onDidRunText = this._register(new Emitter<void>());
|
||||
readonly onDidRunText = this._onDidRunText.event;
|
||||
|
||||
constructor(
|
||||
private readonly _terminalShellTypeContextKey: IContextKey<string>,
|
||||
@@ -974,6 +976,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent default when shift+tab is being sent to the terminal to avoid it bubbling up
|
||||
// and changing focus https://github.com/microsoft/vscode/issues/188329
|
||||
if (event.key === 'Tab' && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Always have alt+F4 skip the terminal on Windows and allow it to be handled by the
|
||||
// system
|
||||
if (isWindows && event.altKey && event.key === 'F4' && !event.ctrlKey) {
|
||||
@@ -1255,6 +1264,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this._onDidInputData.fire(this);
|
||||
this.xterm?.suggestController?.handleNonXtermData(text);
|
||||
this.xterm?.scrollToBottom();
|
||||
this._onDidRunText.fire();
|
||||
}
|
||||
|
||||
async sendPath(originalPath: string | URI, addNewLine: boolean): Promise<void> {
|
||||
|
||||
@@ -120,6 +120,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID
|
||||
readonly raw: RawXtermTerminal;
|
||||
private _core: IXtermCore;
|
||||
private static _suggestedRendererType: 'canvas' | 'dom' | undefined = undefined;
|
||||
private static _checkedWebglCompatible = false;
|
||||
private _attached?: { container: HTMLElement; options: IXtermAttachToElementOptions };
|
||||
private _isPhysicalMouseWheel = MouseWheelClassifier.INSTANCE.isPhysicalMouseWheel();
|
||||
|
||||
@@ -677,6 +678,25 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID
|
||||
if (!this.raw.element || this._webglAddon) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the the WebGL renderer is compatible with xterm.js:
|
||||
// - https://github.com/microsoft/vscode/issues/190195
|
||||
// - https://github.com/xtermjs/xterm.js/issues/4665
|
||||
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1476475
|
||||
if (!XtermTerminal._checkedWebglCompatible) {
|
||||
XtermTerminal._checkedWebglCompatible = true;
|
||||
const checkCanvas = document.createElement('canvas');
|
||||
const checkGl = checkCanvas.getContext('webgl2');
|
||||
const debugInfo = checkGl?.getExtension('WEBGL_debug_renderer_info');
|
||||
if (checkGl && debugInfo) {
|
||||
const renderer = checkGl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
||||
if (renderer.startsWith('ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)')) {
|
||||
this._disableWebglForThisSession();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Addon = await this._getWebglAddonConstructor();
|
||||
this._webglAddon = new Addon();
|
||||
this._disposeOfCanvasRenderer();
|
||||
@@ -701,12 +721,16 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID
|
||||
if (!neverMeasureRenderTime && this._configHelper.config.gpuAcceleration !== 'off') {
|
||||
this._measureRenderTime();
|
||||
}
|
||||
XtermTerminal._suggestedRendererType = 'canvas';
|
||||
this._disposeOfWebglRenderer();
|
||||
this._enableCanvasRenderer();
|
||||
this._disableWebglForThisSession();
|
||||
}
|
||||
}
|
||||
|
||||
private _disableWebglForThisSession() {
|
||||
XtermTerminal._suggestedRendererType = 'canvas';
|
||||
this._disposeOfWebglRenderer();
|
||||
this._enableCanvasRenderer();
|
||||
}
|
||||
|
||||
private async _enableCanvasRenderer(): Promise<void> {
|
||||
if (!this.raw.element || this._canvasAddon) {
|
||||
return;
|
||||
|
||||
@@ -609,6 +609,17 @@ const terminalConfiguration: IConfigurationNode = {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
[TerminalSettingId.FocusAfterRun]: {
|
||||
markdownDescription: localize('terminal.integrated.focusAfterRun', "Controls whether the terminal, accessible buffer, or neither will be focused after `Terminal: Run Selected Text In Active Terminal` has been run."),
|
||||
enum: ['terminal', 'accessible-buffer', 'none'],
|
||||
default: 'none',
|
||||
tags: ['accessibility'],
|
||||
markdownEnumDescriptions: [
|
||||
localize('terminal.integrated.focusAfterRun.terminal', "Always focus the terminal."),
|
||||
localize('terminal.integrated.focusAfterRun.accessible-buffer', "Always focus the accessible buffer."),
|
||||
localize('terminal.integrated.focusAfterRun.none', "Do nothing."),
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -256,7 +256,9 @@ suite('XtermTerminal', () => {
|
||||
});
|
||||
|
||||
suite('renderers', () => {
|
||||
test('should re-evaluate gpu acceleration auto when the setting is changed', async () => {
|
||||
// This is skipped until the webgl renderer bug is fixed in Chromium
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1476475
|
||||
test.skip('should re-evaluate gpu acceleration auto when the setting is changed', async () => {
|
||||
// Check initial state
|
||||
strictEqual(TestWebglAddon.isEnabled, false);
|
||||
|
||||
|
||||
+12
-2
@@ -7,11 +7,12 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalSettingId, terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal';
|
||||
import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
|
||||
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
|
||||
import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
@@ -59,9 +60,18 @@ class AccessibleBufferContribution extends DisposableStore implements ITerminalC
|
||||
private readonly _instance: ITerminalInstance,
|
||||
processManager: ITerminalProcessManager,
|
||||
widgetManager: TerminalWidgetManager,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this.add(_instance.onDidRunText(() => {
|
||||
const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun);
|
||||
if (focusAfterRun === 'terminal') {
|
||||
_instance.focus(true);
|
||||
} else if (focusAfterRun === 'accessible-buffer') {
|
||||
this.show();
|
||||
}
|
||||
}));
|
||||
}
|
||||
layout(xterm: IXtermTerminal & { raw: Terminal }): void {
|
||||
this._xterm = xterm;
|
||||
|
||||
+10
-2
@@ -11,7 +11,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ShellIntegrationStatus, WindowsShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { ShellIntegrationStatus, TerminalSettingId, WindowsShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
|
||||
import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
|
||||
import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
@@ -60,7 +60,14 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc
|
||||
const kb = this._keybindingService.lookupKeybindings(commandId);
|
||||
// Run recent command has multiple keybindings. lookupKeybinding just returns the first one regardless of the when context.
|
||||
// Thus, we have to check if accessibility mode is enabled to determine which keybinding to use.
|
||||
return this._accessibilityService.isScreenReaderOptimized() ? format(msg, kb[1].getAriaLabel()) : format(msg, kb[0].getAriaLabel());
|
||||
const isScreenReaderOptimized = this._accessibilityService.isScreenReaderOptimized();
|
||||
if (isScreenReaderOptimized && kb[1]) {
|
||||
format(msg, kb[1].getAriaLabel());
|
||||
} else if (kb[0]) {
|
||||
format(msg, kb[0].getAriaLabel());
|
||||
} else {
|
||||
return format(noKbMsg, commandId);
|
||||
}
|
||||
}
|
||||
const kb = this._keybindingService.lookupKeybinding(commandId, this._contextKeyService)?.getAriaLabel();
|
||||
return !kb ? format(noKbMsg, commandId) : format(msg, kb);
|
||||
@@ -86,6 +93,7 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc
|
||||
}
|
||||
content.push(this._descriptionForCommand(TerminalCommandId.OpenDetectedLink, localize('openDetectedLink', 'The Open Detected Link ({0}) command enables screen readers to easily open links found in the terminal.'), localize('openDetectedLinkNoKb', 'The Open Detected Link command enables screen readers to easily open links found in the terminal and is currently not triggerable by a keybinding.')));
|
||||
content.push(this._descriptionForCommand(TerminalCommandId.NewWithProfile, localize('newWithProfile', 'The Create New Terminal (With Profile) ({0}) command allows for easy terminal creation using a specific profile.'), localize('newWithProfileNoKb', 'The Create New Terminal (With Profile) command allows for easy terminal creation using a specific profile and is currently not triggerable by a keybinding.')));
|
||||
content.push(localize('focusAfterRun', 'Configure what gets focused after running selected text in the terminal with `{0}`.', TerminalSettingId.FocusAfterRun));
|
||||
content.push(localize('accessibilitySettings', 'Access accessibility settings such as `terminal.integrated.tabFocusMode` via the Preferences: Open Accessibility Settings command.'));
|
||||
return content.join('\n\n');
|
||||
}
|
||||
|
||||
+1
-5
@@ -118,17 +118,13 @@ export abstract class TerminalAccessibleWidget extends DisposableStore {
|
||||
// On escape, hide the accessible buffer and force focus onto the terminal
|
||||
this.hide(true);
|
||||
break;
|
||||
case KeyCode.Tab:
|
||||
// On tab or shift+tab, hide the accessible buffer and perform the default tab
|
||||
// behavior
|
||||
this.hide();
|
||||
break;
|
||||
}
|
||||
}));
|
||||
this.add(this._editorWidget.onDidFocusEditorText(async () => {
|
||||
this._terminalService.setActiveInstance(this._instance as ITerminalInstance);
|
||||
this._xtermElement.classList.add(ClassName.Hide);
|
||||
}));
|
||||
this.add(this._editorWidget.onDidBlurEditorText(async () => this.hide()));
|
||||
}
|
||||
|
||||
registerListeners(): void {
|
||||
|
||||
@@ -100,7 +100,7 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon {
|
||||
this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: no line`);
|
||||
return;
|
||||
}
|
||||
if (!!currentCommand.commandStartX) {
|
||||
if (currentCommand.commandStartX !== undefined) {
|
||||
this._currentCommand = commandLine.substring(currentCommand.commandStartX);
|
||||
this._cursorX = buffer.cursorX - currentCommand.commandStartX;
|
||||
} else {
|
||||
|
||||
@@ -63,9 +63,11 @@ function generateLinkSuffixRegex(eolOnly: boolean) {
|
||||
|
||||
// The comments in the regex below use real strings/numbers for better readability, here's
|
||||
// the legend:
|
||||
// - Path = foo
|
||||
// - Row = 339
|
||||
// - Col = 12
|
||||
// - Path = foo
|
||||
// - Row = 339
|
||||
// - Col = 12
|
||||
// - RowEnd = 341
|
||||
// - ColEnd = 14
|
||||
//
|
||||
// These all support single quote ' in the place of " and [] in the place of ()
|
||||
const lineAndColumnRegexClauses = [
|
||||
@@ -78,7 +80,9 @@ function generateLinkSuffixRegex(eolOnly: boolean) {
|
||||
// "foo",339
|
||||
// "foo",339:12
|
||||
// "foo",339.12
|
||||
`(?::| |['"],)${r()}([:.]${c()})?` + eolSuffix,
|
||||
// "foo",339.12-14
|
||||
// "foo",339.12-341.14
|
||||
`(?::| |['"],)${r()}([:.]${c()}(?:-(?:${re()}\.)?${ce()})?)?` + eolSuffix,
|
||||
// The quotes below are optional [#171652]
|
||||
// "foo", line 339 [#40468]
|
||||
// "foo", line 339, col 12
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user