Merge branch 'main' into issue186399

This commit is contained in:
Doma
2023-08-04 14:34:09 +08:00
committed by GitHub
42 changed files with 1039 additions and 1016 deletions
@@ -1,7 +1,7 @@
parameters:
- name: channel
type: string
default: 1.65.0
default: 1.71.0
- name: targets
default: []
type: object
@@ -1,7 +1,7 @@
parameters:
- name: channel
type: string
default: 1.65.0
default: 1.71.0
- name: targets
default: []
type: object
+31 -54
View File
@@ -183,17 +183,6 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -301,11 +290,8 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"time 0.1.44",
"wasm-bindgen",
"winapi",
]
@@ -356,7 +342,6 @@ name = "code-cli"
version = "0.1.0"
dependencies = [
"async-trait",
"atty",
"base64 0.21.2",
"bytes",
"cfg-if",
@@ -536,9 +521,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.78"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4"
checksum = "e88abab2f5abbe4c56e8f1fb431b784d710b709888f35755a160e62e33fe38e8"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -548,9 +533,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.78"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199"
checksum = "5c0c11acd0e63bae27dcd2afced407063312771212b7a823b4fd72d633be30fb"
dependencies = [
"cc",
"codespan-reporting",
@@ -558,24 +543,24 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
"syn 1.0.103",
"syn 2.0.18",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.78"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c"
checksum = "8d3816ed957c008ccd4728485511e3d9aaf7db419aa321e3d2c5a2f3411e36c8"
[[package]]
name = "cxxbridge-macro"
version = "1.0.78"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea"
checksum = "a26acccf6f445af85ea056362561a24ef56cdc15fcc685f03aec50b9c702cb6d"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.103",
"syn 2.0.18",
]
[[package]]
@@ -1069,23 +1054,23 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.51"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
@@ -1234,9 +1219,9 @@ checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "link-cplusplus"
version = "1.0.7"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
dependencies = [
"cc",
]
@@ -1996,9 +1981,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.2"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
[[package]]
name = "secret-service"
@@ -2271,9 +2256,9 @@ dependencies = [
[[package]]
name = "termcolor"
version = "1.1.3"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
@@ -2298,17 +2283,6 @@ dependencies = [
"syn 2.0.18",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.21"
@@ -2649,12 +2623,6 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -2790,6 +2758,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-sys"
version = "0.36.1"
@@ -3100,7 +3077,7 @@ dependencies = [
"crc32fast",
"crossbeam-utils",
"flate2",
"time 0.3.21",
"time",
]
[[package]]
+1 -2
View File
@@ -29,10 +29,9 @@ rmp-serde = "1.1.1"
uuid = { version = "1.3.3", features = ["serde", "v4"] }
dirs = "5.0.1"
rand = "0.8.5"
atty = "0.2.14"
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
serde_bytes = "0.11.9"
chrono = { version = "0.4.26", features = ["serde"] }
chrono = { version = "0.4.26", features = ["serde", "std", "clock"], default-features = false }
gethostname = "0.4.3"
libc = "0.2.144"
tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "2621784a9ad72aa39500372391332a14bad581a3", default-features = false, features = ["connections"] }
-3
View File
@@ -465,20 +465,17 @@ pub async fn forward(
// #region singleton handler
let auth = Auth::new(&ctx.paths, ctx.log.clone());
println!("preauth {:?}", forward_args.login);
if let (Some(p), Some(at)) = (
forward_args.login.provider.take(),
forward_args.login.access_token.take(),
) {
auth.login(Some(p.into()), Some(at)).await?;
}
println!("auth done");
let mut tunnels = DevTunnels::new_port_forwarding(&ctx.log, auth, &ctx.paths);
let tunnel = tunnels
.start_new_launcher_tunnel(None, true, &forward_args.ports)
.await?;
println!("made tunnel");
forwarding::server(ctx.log, tunnel, server, own_ports_rx, shutdown).await?;
+2 -2
View File
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
use std::collections::HashMap;
use std::{collections::HashMap, io::IsTerminal};
use const_format::concatcp;
use lazy_static::lazy_static;
@@ -119,7 +119,7 @@ lazy_static! {
option_env!("VSCODE_CLI_SERVER_NAME_MAP").and_then(|s| serde_json::from_str(s).unwrap());
/// Whether i/o interactions are allowed in the current CLI.
pub static ref IS_A_TTY: bool = atty::is(atty::Stream::Stdin);
pub static ref IS_A_TTY: bool = std::io::stdin().is_terminal();
/// Whether i/o interactions are allowed in the current CLI.
pub static ref COLORS_ENABLED: bool = *IS_A_TTY && std::env::var(NO_COLOR_ENV).is_err();
+9
View File
@@ -541,6 +541,15 @@ impl<'a> ServerBuilder<'a> {
debug!(self.logger, "Starting server with command... {:?}", cmd);
// On Windows spawning a code-server binary will run cmd.exe /c C:\path\to\code-server.cmd...
// This spawns a cmd.exe window for the user, which if they close will kill the code-server process
// and disconnect the tunnel. To prevent this, pass the CREATE_NO_WINDOW flag to the Command
// only on Windows.
// Original issue: https://github.com/microsoft/vscode/issues/184058
// Partial fix: https://github.com/microsoft/vscode/pull/184621
#[cfg(target_os = "windows")]
let cmd = cmd.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
let child = cmd
.stderr(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
@@ -200,10 +200,11 @@ table > tbody > tr + tr > td {
}
blockquote {
margin: 0 7px 0 5px;
padding: 0 16px 0 10px;
margin: 0;
padding: 2px 16px 0 10px;
border-left-width: 5px;
border-left-style: solid;
border-radius: 2px;
}
code {
@@ -232,6 +233,7 @@ pre code {
pre {
background-color: var(--vscode-textCodeBlock-background);
border: 1px solid var(--vscode-widget-border);
}
.vscode-high-contrast h1 {
+103 -110
View File
@@ -3,130 +3,123 @@
"name": "Default Dark Modern",
"include": "./dark_plus.json",
"colors": {
"activityBar.activeBorder": "#0078d4",
"activityBar.activeBorder": "#0078D4",
"activityBar.background": "#181818",
"activityBar.border": "#ffffff15",
"activityBar.foreground": "#d7d7d7",
"activityBar.inactiveForeground": "#ffffff80",
"activityBarBadge.background": "#0078d4",
"activityBarBadge.foreground": "#ffffff",
"badge.background": "#4D4D4D",
"badge.foreground": "#f2f2f2",
"button.background": "#0078d4",
"button.border": "#ffffff12",
"button.foreground": "#ffffff",
"button.hoverBackground": "#0078d4e6",
"button.secondaryBackground": "#FFFFFF0F",
"button.secondaryForeground": "#cccccc",
"button.secondaryHoverBackground": "#ffffff15",
"activityBar.border": "#2B2B2B",
"activityBar.foreground": "#D7D7D7",
"activityBar.inactiveForeground": "#868686",
"activityBarBadge.background": "#0078D4",
"activityBarBadge.foreground": "#FFFFFF",
"badge.background": "#616161",
"badge.foreground": "#F8F8F8",
"button.background": "#0078D4",
"button.border": "#FFFFFF12",
"button.foreground": "#FFFFFF",
"button.hoverBackground": "#026EC1",
"button.secondaryBackground": "#1F1F1F",
"button.secondaryForeground": "#CCCCCC",
"button.secondaryHoverBackground": "#2B2B2B",
"checkbox.background": "#313131",
"checkbox.border": "#ffffff1f",
"checkbox.border": "#3C3C3C",
"debugToolBar.background": "#181818",
"descriptionForeground": "#8b949e",
"descriptionForeground": "#9D9D9D",
"dropdown.background": "#313131",
"dropdown.border": "#ffffff1f",
"dropdown.foreground": "#cccccc",
"dropdown.listBackground": "#1f1f1f",
"editor.background": "#1f1f1f",
"editor.findMatchBackground": "#9e6a03",
"editor.foreground": "#cccccc",
"editorGroup.border": "#ffffff17",
"dropdown.border": "#3C3C3C",
"dropdown.foreground": "#CCCCCC",
"dropdown.listBackground": "#1F1F1F",
"editor.background": "#1F1F1F",
"editor.findMatchBackground": "#9E6A03",
"editor.foreground": "#CCCCCC",
"editorGroup.border": "#FFFFFF17",
"editorGroupHeader.tabsBackground": "#181818",
"editorGroupHeader.tabsBorder": "#ffffff15",
"editorGutter.addedBackground": "#2ea043",
"editorGutter.deletedBackground": "#f85149",
"editorGutter.modifiedBackground": "#0078d4",
"editorLineNumber.activeForeground": "#cccccc",
"editorLineNumber.foreground": "#6e7681",
"editorGroupHeader.tabsBorder": "#2B2B2B",
"editorGutter.addedBackground": "#2EA043",
"editorGutter.deletedBackground": "#F85149",
"editorGutter.modifiedBackground": "#0078D4",
"editorLineNumber.activeForeground": "#CCCCCC",
"editorLineNumber.foreground": "#6E7681",
"editorOverviewRuler.border": "#010409",
"editorWidget.background": "#1f1f1f",
"errorForeground": "#f85149",
"focusBorder": "#0078d4",
"foreground": "#cccccc",
"icon.foreground": "#cccccc",
"input.background": "#2a2a2a",
"input.border": "#ffffff1f",
"input.foreground": "#cccccc",
"input.placeholderForeground": "#ffffff79",
"inputOption.activeBackground": "#2489db82",
"inputOption.activeBorder": "#2488db",
"keybindingLabel.foreground": "#cccccc",
"list.activeSelectionBackground": "#323232",
"list.activeSelectionIconForeground": "#ffffff",
"list.activeSelectionForeground": "#ffffff",
"menu.background": "#1f1f1f",
"notificationCenterHeader.background": "#1f1f1f",
"notificationCenterHeader.foreground": "#cccccc",
"notifications.background": "#1f1f1f",
"notifications.border": "#ffffff15",
"notifications.foreground": "#cccccc",
"editorWidget.background": "#2B2B2B",
"errorForeground": "#F85149",
"focusBorder": "#0078D4",
"foreground": "#CCCCCC",
"icon.foreground": "#CCCCCC",
"input.background": "#2A2A2A",
"input.border": "#3C3C3C",
"input.foreground": "#CCCCCC",
"input.placeholderForeground": "#9D9D9D",
"inputOption.activeBackground": "#2489DB82",
"inputOption.activeBorder": "#2488DB",
"keybindingLabel.foreground": "#CCCCCC",
"menu.background": "#1F1F1F",
"notificationCenterHeader.background": "#1F1F1F",
"notificationCenterHeader.foreground": "#CCCCCC",
"notifications.background": "#1F1F1F",
"notifications.border": "#2B2B2B",
"notifications.foreground": "#CCCCCC",
"panel.background": "#181818",
"panel.border": "#ffffff15",
"panelInput.border": "#ffffff15",
"panelTitle.activeBorder": "#0078d4",
"panelTitle.activeForeground": "#cccccc",
"panelTitle.inactiveForeground": "#8b949e",
"peekViewEditor.background": "#1f1f1f",
"peekViewEditor.matchHighlightBackground": "#bb800966",
"peekViewResult.background": "#1f1f1f",
"peekViewResult.matchHighlightBackground": "#bb800966",
"pickerGroup.border": "#ffffff15",
"pickerGroup.foreground": "#8b949e",
"progressBar.background": "#0078d4",
"quickInput.background": "#1f1f1f",
"quickInput.foreground": "#cccccc",
"scrollbar.shadow": "#484f5833",
"scrollbarSlider.activeBackground": "#6e768187",
"scrollbarSlider.background": "#6e768133",
"scrollbarSlider.hoverBackground": "#6e768145",
"panel.border": "#2B2B2B",
"panelInput.border": "#2B2B2B",
"panelTitle.activeBorder": "#0078D4",
"panelTitle.activeForeground": "#CCCCCC",
"panelTitle.inactiveForeground": "#9D9D9D",
"peekViewEditor.background": "#1F1F1F",
"peekViewEditor.matchHighlightBackground": "#BB800966",
"peekViewResult.background": "#1F1F1F",
"peekViewResult.matchHighlightBackground": "#BB800966",
"pickerGroup.border": "#2B2B2B",
"pickerGroup.foreground": "#8B949E",
"progressBar.background": "#0078D4",
"quickInput.background": "#2A2A2A",
"quickInput.foreground": "#CCCCCC",
"settings.dropdownBackground": "#313131",
"settings.dropdownBorder": "#ffffff1f",
"settings.headerForeground": "#ffffff",
"settings.modifiedItemIndicator": "#bb800966",
"settings.dropdownBorder": "#3C3C3C",
"settings.headerForeground": "#FFFFFF",
"settings.modifiedItemIndicator": "#BB800966",
"sideBar.background": "#181818",
"sideBar.border": "#ffffff15",
"sideBar.foreground": "#cccccc",
"sideBar.border": "#2B2B2B",
"sideBar.foreground": "#CCCCCC",
"sideBarSectionHeader.background": "#181818",
"sideBarSectionHeader.border": "#ffffff15",
"sideBarSectionHeader.foreground": "#cccccc",
"sideBarTitle.foreground": "#cccccc",
"sideBarSectionHeader.border": "#2B2B2B",
"sideBarSectionHeader.foreground": "#CCCCCC",
"sideBarTitle.foreground": "#CCCCCC",
"statusBar.background": "#181818",
"statusBar.border": "#ffffff15",
"statusBar.debuggingBackground": "#0078d4",
"statusBar.debuggingForeground": "#ffffff",
"statusBar.focusBorder": "#0078d4",
"statusBar.foreground": "#cccccc",
"statusBar.noFolderBackground": "#1f1f1f",
"statusBarItem.focusBorder": "#0078d4",
"statusBarItem.prominentBackground": "#6e768166",
"statusBarItem.remoteBackground": "#0078d4",
"statusBarItem.remoteForeground": "#ffffff",
"tab.activeBackground": "#1f1f1f",
"tab.activeBorder": "#1f1f1f",
"tab.activeBorderTop": "#0078d4",
"tab.activeForeground": "#ffffff",
"tab.border": "#ffffff15",
"tab.hoverBackground": "#1f1f1f",
"statusBar.border": "#2B2B2B",
"statusBar.debuggingBackground": "#0078D4",
"statusBar.debuggingForeground": "#FFFFFF",
"statusBar.focusBorder": "#0078D4",
"statusBar.foreground": "#CCCCCC",
"statusBar.noFolderBackground": "#1F1F1F",
"statusBarItem.focusBorder": "#0078D4",
"statusBarItem.prominentBackground": "#6E768166",
"statusBarItem.remoteBackground": "#0078D4",
"statusBarItem.remoteForeground": "#FFFFFF",
"tab.activeBackground": "#1F1F1F",
"tab.activeBorder": "#1F1F1F",
"tab.activeBorderTop": "#0078D4",
"tab.activeForeground": "#FFFFFF",
"tab.border": "#2B2B2B",
"tab.hoverBackground": "#1F1F1F",
"tab.inactiveBackground": "#181818",
"tab.inactiveForeground": "#ffffff80",
"tab.unfocusedActiveBorder": "#1f1f1f",
"tab.unfocusedActiveBorderTop": "#ffffff15",
"tab.unfocusedHoverBackground": "#6e76811a",
"terminal.foreground": "#cccccc",
"terminal.tab.activeBorder": "#0078d4",
"textBlockQuote.background": "#010409",
"textBlockQuote.border": "#ffffff14",
"textCodeBlock.background": "#6e768166",
"tab.inactiveForeground": "#9D9D9D",
"tab.unfocusedActiveBorder": "#1F1F1F",
"tab.unfocusedActiveBorderTop": "#2B2B2B",
"tab.unfocusedHoverBackground": "#1F1F1F",
"terminal.foreground": "#CCCCCC",
"terminal.tab.activeBorder": "#0078D4",
"textBlockQuote.background": "#2B2B2B",
"textBlockQuote.border": "#616161",
"textCodeBlock.background": "#2B2B2B",
"textLink.activeForeground": "#40A6FF",
"textLink.foreground": "#40A6FF",
"textSeparator.foreground": "#21262d",
"textSeparator.foreground": "#21262D",
"titleBar.activeBackground": "#181818",
"titleBar.activeForeground": "#cccccc",
"titleBar.border": "#ffffff15",
"titleBar.inactiveBackground": "#1f1f1f",
"titleBar.inactiveForeground": "#8b949e",
"welcomePage.tileBackground": "#ffffff0f",
"welcomePage.progress.foreground": "#0078d4",
"widget.border": "#ffffff15",
"titleBar.activeForeground": "#CCCCCC",
"titleBar.border": "#2B2B2B",
"titleBar.inactiveBackground": "#1F1F1F",
"titleBar.inactiveForeground": "#9D9D9D",
"welcomePage.tileBackground": "#2B2B2B",
"welcomePage.progress.foreground": "#0078D4",
"widget.border": "#333333",
},
}
+100 -103
View File
@@ -4,141 +4,138 @@
"include": "./light_plus.json",
"colors": {
"activityBar.activeBorder": "#005FB8",
"activityBar.background": "#f8f8f8",
"activityBar.border": "#0000001a",
"activityBar.foreground": "#1f1f1f",
"activityBar.inactiveForeground": "#0000009e",
"activityBar.background": "#F8F8F8",
"activityBar.border": "#E5E5E5",
"activityBar.foreground": "#1F1F1F",
"activityBar.inactiveForeground": "#616161",
"activityBarBadge.background": "#005FB8",
"activityBarBadge.foreground": "#ffffff",
"activityBarBadge.foreground": "#FFFFFF",
"badge.background": "#CCCCCC",
"badge.foreground": "#3b3b3b",
"badge.foreground": "#3B3B3B",
"button.background": "#005FB8",
"button.border": "#0000001a",
"button.foreground": "#ffffff",
"button.hoverBackground": "#005FB8e6",
"button.secondaryBackground": "#0000001a",
"button.secondaryForeground": "#3b3b3b",
"button.secondaryHoverBackground": "#00000034",
"checkbox.background": "#f8f8f8",
"button.foreground": "#FFFFFF",
"button.hoverBackground": "#0258A8",
"button.secondaryBackground": "#E5E5E5",
"button.secondaryForeground": "#3B3B3B",
"button.secondaryHoverBackground": "#CBCBCB",
"checkbox.background": "#F8F8F8",
"checkbox.border": "#CECECE",
"descriptionForeground": "#3b3b3b",
"dropdown.background": "#ffffff",
"descriptionForeground": "#3B3B3B",
"dropdown.background": "#FFFFFF",
"dropdown.border": "#CECECE",
"dropdown.foreground": "#3b3b3b",
"dropdown.listBackground": "#ffffff",
"editor.background": "#ffffff",
"editor.foreground": "#3b3b3b",
"dropdown.foreground": "#3B3B3B",
"dropdown.listBackground": "#FFFFFF",
"editor.background": "#FFFFFF",
"editor.foreground": "#3B3B3B",
"editor.inactiveSelectionBackground": "#E5EBF1",
"editor.selectionHighlightBackground": "#ADD6FF80",
"editorGroup.border": "#0000001a",
"editorGroupHeader.tabsBackground": "#f8f8f8",
"editorGroupHeader.tabsBorder": "#0000001a",
"editorGutter.addedBackground": "#2ea043",
"editorGutter.deletedBackground": "#f85149",
"editorGroup.border": "#E5E5E5",
"editorGroupHeader.tabsBackground": "#F8F8F8",
"editorGroupHeader.tabsBorder": "#E5E5E5",
"editorGutter.addedBackground": "#2EA043",
"editorGutter.deletedBackground": "#F85149",
"editorGutter.modifiedBackground": "#005FB8",
"editorIndentGuide.background": "#D3D3D3",
"editorLineNumber.activeForeground": "#171184",
"editorLineNumber.foreground": "#6e7681",
"editorOverviewRuler.border": "#0000001a",
"editorSuggestWidget.background": "#f8f8f8",
"editorWidget.background": "#ffffff",
"errorForeground": "#f85149",
"editorLineNumber.foreground": "#6E7681",
"editorOverviewRuler.border": "#E5E5E5",
"editorSuggestWidget.background": "#F8F8F8",
"editorWidget.background": "#F8F8F8",
"errorForeground": "#F85149",
"focusBorder": "#005FB8",
"foreground": "#3b3b3b",
"icon.foreground": "#3b3b3b",
"input.background": "#ffffff",
"foreground": "#3B3B3B",
"icon.foreground": "#3B3B3B",
"input.background": "#FFFFFF",
"input.border": "#CECECE",
"input.foreground": "#3b3b3b",
"input.placeholderForeground": "#00000079",
"inputOption.activeBackground": "#005fb841",
"input.foreground": "#3B3B3B",
"input.placeholderForeground": "#868686",
"inputOption.activeBackground": "#BED6ED",
"inputOption.activeBorder": "#005FB8",
"inputOption.activeForeground": "#000000",
"keybindingLabel.foreground": "#3b3b3b",
"list.activeSelectionBackground": "#e8e8e8",
"keybindingLabel.foreground": "#3B3B3B",
"list.activeSelectionBackground": "#E8E8E8",
"list.activeSelectionForeground": "#000000",
"list.activeSelectionIconForeground": "#000000",
"list.hoverBackground": "#f2f2f2",
"menu.border": "#D4D4D4",
"notebook.cellBorderColor": "#E8E8E8",
"notebook.selectedCellBackground": "#c8ddf150",
"notificationCenterHeader.background": "#ffffff",
"notificationCenterHeader.foreground": "#3b3b3b",
"notifications.background": "#ffffff",
"notifications.border": "#0000001a",
"notifications.foreground": "#3b3b3b",
"panel.background": "#f8f8f8",
"panel.border": "#0000001a",
"panelInput.border": "#0000001a",
"list.hoverBackground": "#F2F2F2",
"list.focusAndSelectionOutline": "#005FB8",
"menu.border": "#CECECE",
"notebook.cellBorderColor": "#E5E5E5",
"notebook.selectedCellBackground": "#C8DDF150",
"notificationCenterHeader.background": "#FFFFFF",
"notificationCenterHeader.foreground": "#3B3B3B",
"notifications.background": "#FFFFFF",
"notifications.border": "#E5E5E5",
"notifications.foreground": "#3B3B3B",
"panel.background": "#F8F8F8",
"panel.border": "#E5E5E5",
"panelInput.border": "#E5E5E5",
"panelTitle.activeBorder": "#005FB8",
"panelTitle.activeForeground": "#3b3b3b",
"panelTitle.inactiveForeground": "#3b3b3b",
"peekViewEditor.matchHighlightBackground": "#bb800966",
"peekViewResult.background": "#ffffff",
"peekViewResult.matchHighlightBackground": "#bb800966",
"pickerGroup.border": "#0000001a",
"pickerGroup.foreground": "#8b949e",
"panelTitle.activeForeground": "#3B3B3B",
"panelTitle.inactiveForeground": "#3B3B3B",
"peekViewEditor.matchHighlightBackground": "#BB800966",
"peekViewResult.background": "#FFFFFF",
"peekViewResult.matchHighlightBackground": "#BB800966",
"pickerGroup.border": "#E5E5E5",
"pickerGroup.foreground": "#8B949E",
"ports.iconRunningProcessForeground": "#369432",
"progressBar.background": "#005FB8",
"quickInput.background": "#ffffff",
"quickInput.foreground": "#3b3b3b",
"scrollbar.shadow": "#484f5833",
"scrollbarSlider.activeBackground": "#6e768187",
"scrollbarSlider.background": "#6e768133",
"scrollbarSlider.hoverBackground": "#6e768145",
"quickInput.background": "#F8F8F8",
"quickInput.foreground": "#3B3B3B",
"searchEditor.textInputBorder": "#CECECE",
"settings.dropdownBackground": "#ffffff",
"settings.dropdownBackground": "#FFFFFF",
"settings.dropdownBorder": "#CECECE",
"settings.headerForeground": "#1f1f1f",
"settings.modifiedItemIndicator": "#bb800966",
"settings.headerForeground": "#1F1F1F",
"settings.modifiedItemIndicator": "#BB800966",
"settings.numberInputBorder": "#CECECE",
"settings.textInputBorder": "#CECECE",
"sideBar.background": "#f8f8f8",
"sideBar.border": "#0000001a",
"sideBar.foreground": "#3b3b3b",
"sideBarSectionHeader.background": "#f8f8f8",
"sideBarSectionHeader.border": "#0000001a",
"sideBarSectionHeader.foreground": "#3b3b3b",
"sideBarTitle.foreground": "#3b3b3b",
"statusBar.background": "#f8f8f8",
"statusBar.foreground": "#3b3b3b",
"statusBar.border": "#0000001a",
"statusBar.debuggingBackground": "#fd716c",
"sideBar.background": "#F8F8F8",
"sideBar.border": "#E5E5E5",
"sideBar.foreground": "#3B3B3B",
"sideBarSectionHeader.background": "#F8F8F8",
"sideBarSectionHeader.border": "#E5E5E5",
"sideBarSectionHeader.foreground": "#3B3B3B",
"sideBarTitle.foreground": "#3B3B3B",
"statusBar.background": "#F8F8F8",
"statusBar.foreground": "#3B3B3B",
"statusBar.border": "#E5E5E5",
"statusBar.debuggingBackground": "#FD716C",
"statusBar.debuggingForeground": "#000000",
"statusBar.focusBorder": "#005FB8",
"statusBar.noFolderBackground": "#f8f8f8",
"statusBarItem.errorBackground": "#c72e0f",
"statusBar.noFolderBackground": "#F8F8F8",
"statusBarItem.errorBackground": "#C72E0F",
"statusBarItem.focusBorder": "#005FB8",
"statusBarItem.prominentBackground": "#6e768166",
"statusBarItem.prominentBackground": "#6E768166",
"statusBarItem.remoteBackground": "#005FB8",
"statusBarItem.remoteForeground": "#ffffff",
"tab.activeBackground": "#ffffff",
"tab.activeBorder": "#f8f8f8",
"statusBarItem.remoteForeground": "#FFFFFF",
"tab.activeBackground": "#FFFFFF",
"tab.activeBorder": "#F8F8F8",
"tab.activeBorderTop": "#005FB8",
"tab.activeForeground": "#3b3b3b",
"tab.border": "#0000001a",
"tab.hoverBackground": "#ffffff",
"tab.inactiveBackground": "#f8f8f8",
"tab.inactiveForeground": "#00000080",
"tab.lastPinnedBorder": "#3b3b3b30",
"tab.unfocusedActiveBorder": "#f8f8f8",
"tab.unfocusedActiveBorderTop": "#00000015",
"tab.unfocusedHoverBackground": "#6e76811a",
"tab.activeForeground": "#3B3B3B",
"tab.border": "#E5E5E5",
"tab.hoverBackground": "#FFFFFF",
"tab.inactiveBackground": "#F8F8F8",
"tab.inactiveForeground": "#868686",
"tab.lastPinnedBorder": "#D4D4D4",
"tab.unfocusedActiveBorder": "#F8F8F8",
"tab.unfocusedActiveBorderTop": "#E5E5E5",
"tab.unfocusedHoverBackground": "#F8F8F8",
"terminalCursor.foreground": "#005FB8",
"terminal.foreground": "#3b3b3b",
"terminal.foreground": "#3B3B3B",
"terminal.inactiveSelectionBackground": "#E5EBF1",
"terminal.tab.activeBorder": "#005fb8",
"textBlockQuote.background": "#f8f8f8",
"textBlockQuote.border": "#0000001a",
"textCodeBlock.background": "#f2f2f2",
"terminal.tab.activeBorder": "#005FB8",
"textBlockQuote.background": "#F8F8F8",
"textBlockQuote.border": "#E5E5E5",
"textCodeBlock.background": "#F8F8F8",
"textLink.activeForeground": "#005FB8",
"textLink.foreground": "#005FB8",
"textSeparator.foreground": "#21262d",
"titleBar.activeBackground": "#f8f8f8",
"titleBar.activeForeground": "#1e1e1e",
"titleBar.border": "#0000001a",
"titleBar.inactiveBackground": "#f8f8f8",
"titleBar.inactiveForeground": "#8b949e",
"welcomePage.tileBackground": "#f3f3f3",
"widget.border": "#0000001a",
"textSeparator.foreground": "#21262D",
"titleBar.activeBackground": "#F8F8F8",
"titleBar.activeForeground": "#1E1E1E",
"titleBar.border": "#E5E5E5",
"titleBar.inactiveBackground": "#F8F8F8",
"titleBar.inactiveForeground": "#8B949E",
"welcomePage.tileBackground": "#F3F3F3",
"widget.border": "#E5E5E5"
},
}
+4
View File
@@ -707,6 +707,10 @@ export function tieBreakComparators<TItem>(...comparators: Comparator<TItem>[]):
*/
export const numberComparator: Comparator<number> = (a, b) => a - b;
export function reverseOrder<TItem>(comparator: Comparator<TItem>): Comparator<TItem> {
return (a, b) => -comparator(a, b);
}
/**
* Returns the first item that is equal to or greater than every other item.
*/
@@ -142,9 +142,9 @@ class ViewModel extends Disposable {
/** @description play audio-cue for diff */
const currentViewItem = this.currentElement.read(reader);
if (currentViewItem?.type === LineType.Deleted) {
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted);
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'accessibleDiffViewer.currentElementChanged' });
} else if (currentViewItem?.type === LineType.Added) {
this._audioCueService.playAudioCue(AudioCue.diffLineInserted);
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'accessibleDiffViewer.currentElementChanged' });
}
}));
@@ -255,11 +255,11 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
if (e?.reason === CursorChangeReason.Explicit) {
const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.modifiedRange.contains(e.position.lineNumber));
if (diff?.lineRangeMapping.modifiedRange.isEmpty) {
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted);
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' });
} else if (diff?.lineRangeMapping.originalRange.isEmpty) {
this._audioCueService.playAudioCue(AudioCue.diffLineInserted);
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' });
} else if (diff) {
this._audioCueService.playAudioCue(AudioCue.diffLineModified);
this._audioCueService.playAudioCue(AudioCue.diffLineModified, { source: 'diffEditor.cursorPositionChanged' });
}
}
}));
@@ -460,11 +460,11 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
this._goTo(diff);
if (diff.lineRangeMapping.modifiedRange.isEmpty) {
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted);
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'diffEditor.goToDiff' });
} else if (diff.lineRangeMapping.originalRange.isEmpty) {
this._audioCueService.playAudioCue(AudioCue.diffLineInserted);
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'diffEditor.goToDiff' });
} else if (diff) {
this._audioCueService.playAudioCue(AudioCue.diffLineModified);
this._audioCueService.playAudioCue(AudioCue.diffLineModified, { source: 'diffEditor.goToDiff' });
}
}
@@ -286,7 +286,7 @@ export class ViewZoneManager extends Disposable {
for (const a of alignmentsSyncedMovedText.read(reader) ?? []) {
if (!syncedMovedText?.lineRangeMapping.original.intersect(a.originalRange)
&& !syncedMovedText?.lineRangeMapping.modified.intersect(a.modifiedRange)) {
|| !syncedMovedText?.lineRangeMapping.modified.intersect(a.modifiedRange)) {
// ignore unrelated alignments outside the synced moved text
continue;
}
@@ -224,11 +224,11 @@ export class DiffNavigator extends Disposable implements IDiffNavigator {
}
const insertedOrModified = modifiedEditor.getLineDecorations(lineNumber).find(l => l.options.className === 'line-insert');
if (insertedOrModified) {
this._audioCueService.playAudioCue(AudioCue.diffLineModified, true);
this._audioCueService.playAudioCue(AudioCue.diffLineModified, { allowManyInParallel: true });
} else if (jumpToChange) {
// The modified editor does not include deleted lines, but when
// we are moved to the area where lines were deleted, play this cue
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, true);
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { allowManyInParallel: true });
} else {
return;
}
+2 -2
View File
@@ -325,9 +325,9 @@ export class DiffReview extends Disposable {
}
const element = !type ? current : type === 'next' ? current?.nextElementSibling : current?.previousElementSibling;
if (element?.classList.contains(DiffEditorLineClasses.Insert)) {
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, true);
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { allowManyInParallel: true });
} else if (element?.classList.contains(DiffEditorLineClasses.Delete)) {
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, true);
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { allowManyInParallel: true });
}
this.scrollbar.scanDomNode();
}
@@ -3,8 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { compareBy, 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 { LineRange } from 'vs/editor/common/core/lineRange';
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { Position } from 'vs/editor/common/core/position';
@@ -137,37 +139,9 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
const changes = lineRangeMappingFromRangeMappings(alignments, originalLines, modifiedLines);
const moves: MovedText[] = [];
let moves: MovedText[] = [];
if (options.computeMoves) {
const deletions = changes
.filter(c => c.modifiedRange.isEmpty && c.originalRange.length >= 3)
.map(d => new LineRangeFragment(d.originalRange, originalLines));
const insertions = new Set(changes
.filter(c => c.originalRange.isEmpty && c.modifiedRange.length >= 3)
.map(d => new LineRangeFragment(d.modifiedRange, modifiedLines)));
for (const deletion of deletions) {
let highestSimilarity = -1;
let best: LineRangeFragment | undefined;
for (const insertion of insertions) {
const similarity = deletion.computeSimilarity(insertion);
if (similarity > highestSimilarity) {
highestSimilarity = similarity;
best = insertion;
}
}
if (highestSimilarity > 0.90 && best) {
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
new OffsetRange(deletion.range.startLineNumber - 1, deletion.range.endLineNumberExclusive - 1),
new OffsetRange(best.range.startLineNumber - 1, best.range.endLineNumberExclusive - 1),
), timeout, considerWhitespaceChanges);
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
insertions.delete(best);
moves.push(new MovedText(new SimpleLineRangeMapping(deletion.range, best.range), mappings));
}
}
moves = this.computeMoves(changes, originalLines, modifiedLines, srcDocLines, tgtDocLines, timeout, considerWhitespaceChanges);
}
// Make sure all ranges are valid
@@ -202,6 +176,136 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
return new LinesDiff(changes, moves, hitTimeout);
}
private computeMoves(changes: LineRangeMapping[], originalLines: string[], modifiedLines: string[], hashedOriginalLines: number[], hashedModifiedLines: number[], timeout: ITimeout, considerWhitespaceChanges: boolean): MovedText[] {
const moves: MovedText[] = [];
const deletions = changes
.filter(c => c.modifiedRange.isEmpty && c.originalRange.length >= 3)
.map(d => new LineRangeFragment(d.originalRange, originalLines, d));
const insertions = new Set(changes
.filter(c => c.originalRange.isEmpty && c.modifiedRange.length >= 3)
.map(d => new LineRangeFragment(d.modifiedRange, modifiedLines, d)));
const excludedChanges = new Set<LineRangeMapping>();
for (const deletion of deletions) {
let highestSimilarity = -1;
let best: LineRangeFragment | undefined;
for (const insertion of insertions) {
const similarity = deletion.computeSimilarity(insertion);
if (similarity > highestSimilarity) {
highestSimilarity = similarity;
best = insertion;
}
}
if (highestSimilarity > 0.90 && best) {
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
new OffsetRange(deletion.range.startLineNumber - 1, deletion.range.endLineNumberExclusive - 1),
new OffsetRange(best.range.startLineNumber - 1, best.range.endLineNumberExclusive - 1)
), timeout, considerWhitespaceChanges);
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
insertions.delete(best);
moves.push(new MovedText(new SimpleLineRangeMapping(deletion.range, best.range), mappings));
excludedChanges.add(deletion.source);
excludedChanges.add(best.source);
}
}
const original3LineHashes = new SetMap<string, { range: LineRange }>();
for (const change of changes) {
if (excludedChanges.has(change)) {
continue;
}
for (let i = change.originalRange.startLineNumber; i < change.originalRange.endLineNumberExclusive - 2; i++) {
const key = `${hashedOriginalLines[i - 1]}:${hashedOriginalLines[i + 1 - 1]}:${hashedOriginalLines[i + 2 - 1]}`;
original3LineHashes.add(key, { range: new LineRange(i, i + 3) });
}
}
interface PossibleMapping {
modifiedLineRange: LineRange;
originalLineRange: LineRange;
}
const possibleMappings: PossibleMapping[] = [];
changes.sort(compareBy(c => c.modifiedRange.startLineNumber, numberComparator));
for (const change of changes) {
if (excludedChanges.has(change)) {
continue;
}
let lastMappings: PossibleMapping[] = [];
for (let i = change.modifiedRange.startLineNumber; i < change.modifiedRange.endLineNumberExclusive - 2; i++) {
const key = `${hashedModifiedLines[i - 1]}:${hashedModifiedLines[i + 1 - 1]}:${hashedModifiedLines[i + 2 - 1]}`;
//const isWeakKey = (originalLines[i].trim().length + originalLines[i + 1].trim().length + originalLines[i + 2].trim().length < 20);
const currentModifiedRange = new LineRange(i, i + 3);
const nextMappings: PossibleMapping[] = [];
original3LineHashes.forEach(key, ({ range }) => {
for (const lastMapping of lastMappings) {
// does this match extend some last match?
if (lastMapping.originalLineRange.endLineNumberExclusive + 1 === range.endLineNumberExclusive &&
lastMapping.modifiedLineRange.endLineNumberExclusive + 1 === currentModifiedRange.endLineNumberExclusive) {
lastMapping.originalLineRange = new LineRange(lastMapping.originalLineRange.startLineNumber, range.endLineNumberExclusive);
lastMapping.modifiedLineRange = new LineRange(lastMapping.modifiedLineRange.startLineNumber, currentModifiedRange.endLineNumberExclusive);
nextMappings.push(lastMapping);
return;
}
}
const mapping: PossibleMapping = {
modifiedLineRange: currentModifiedRange,
originalLineRange: range,
};
possibleMappings.push(mapping);
nextMappings.push(mapping);
});
lastMappings = nextMappings;
}
}
possibleMappings.sort(reverseOrder(compareBy(m => m.modifiedLineRange.length, numberComparator)));
const modifiedSet = new LineRangeSet();
const originalSet = new LineRangeSet();
for (const mapping of possibleMappings) {
const diffOrigToMod = mapping.modifiedLineRange.startLineNumber - mapping.originalLineRange.startLineNumber;
const modifiedSections = modifiedSet.subtractFrom(mapping.modifiedLineRange);
const originalTranslatedSections = originalSet.subtractFrom(mapping.originalLineRange).map(r => r.delta(diffOrigToMod));
const modifiedIntersectedSections = intersectRanges(modifiedSections, originalTranslatedSections);
for (const s of modifiedIntersectedSections) {
if (s.length < 3) {
continue;
}
const modifiedLineRange = s;
const originalLineRange = s.delta(-diffOrigToMod);
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
new OffsetRange(originalLineRange.startLineNumber - 1, originalLineRange.endLineNumberExclusive - 1),
new OffsetRange(modifiedLineRange.startLineNumber - 1, modifiedLineRange.endLineNumberExclusive - 1)
), timeout, considerWhitespaceChanges);
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
moves.push(new MovedText(new SimpleLineRangeMapping(originalLineRange, modifiedLineRange), mappings));
modifiedSet.addRange(modifiedLineRange);
originalSet.addRange(originalLineRange);
}
}
return moves;
}
private refineDiff(originalLines: string[], modifiedLines: string[], diff: SequenceDiff, timeout: ITimeout, considerWhitespaceChanges: boolean): { mappings: RangeMapping[]; hitTimeout: boolean } {
const slice1 = new LinesSliceCharSequence(originalLines, diff.seq1Range, considerWhitespaceChanges);
const slice2 = new LinesSliceCharSequence(modifiedLines, diff.seq2Range, considerWhitespaceChanges);
@@ -233,6 +337,100 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
}
}
function intersectRanges(ranges1: LineRange[], ranges2: LineRange[]): LineRange[] {
const result: LineRange[] = [];
let i1 = 0;
let i2 = 0;
while (i1 < ranges1.length && i2 < ranges2.length) {
const r1 = ranges1[i1];
const r2 = ranges2[i2];
const i = r1.intersect(r2);
if (i && !i.isEmpty) {
result.push(i);
}
if (r1.endLineNumberExclusive < r2.endLineNumberExclusive) {
i1++;
} else {
i2++;
}
}
return result;
}
// TODO make this fast
class LineRangeSet {
private readonly _normalizedRanges: LineRange[] = [];
addRange(range: LineRange): void {
// Idea: Find joinRange such that:
// replaceRange = _normalizedRanges.replaceRange(joinRange, range.joinAll(joinRange.map(idx => this._normalizedRanges[idx])))
// idx of first element that touches range or that is after range
const joinRangeStartIdx = mapMinusOne(this._normalizedRanges.findIndex(r => r.endLineNumberExclusive >= range.startLineNumber), this._normalizedRanges.length);
// idx of element after { last element that touches range or that is before range }
const joinRangeEndIdxExclusive = findLastIndex(this._normalizedRanges, r => r.startLineNumber <= range.endLineNumberExclusive) + 1;
if (joinRangeStartIdx === joinRangeEndIdxExclusive) {
// If there is no element that touches range, then joinRangeStartIdx === joinRangeEndIdxExclusive and that value is the index of the element after range
this._normalizedRanges.splice(joinRangeStartIdx, 0, range);
} else if (joinRangeStartIdx === joinRangeEndIdxExclusive - 1) {
// Else, there is an element that touches range and in this case it is both the first and last element. Thus we can replace it
const joinRange = this._normalizedRanges[joinRangeStartIdx];
this._normalizedRanges[joinRangeStartIdx] = joinRange.join(range);
} else {
// First and last element are different - we need to replace the entire range
const joinRange = this._normalizedRanges[joinRangeStartIdx].join(this._normalizedRanges[joinRangeEndIdxExclusive - 1]).join(range);
this._normalizedRanges.splice(joinRangeStartIdx, joinRangeEndIdxExclusive - joinRangeStartIdx, joinRange);
}
}
intersects(range: LineRange): boolean {
for (const r of this._normalizedRanges) {
if (r.intersectsStrict(range)) {
return true;
}
}
return false;
}
/**
* Subtracts all ranges in this set from `range` and returns the result.
*/
subtractFrom(range: LineRange): LineRange[] {
// idx of first element that touches range or that is after range
const joinRangeStartIdx = mapMinusOne(this._normalizedRanges.findIndex(r => r.endLineNumberExclusive >= range.startLineNumber), this._normalizedRanges.length);
// idx of element after { last element that touches range or that is before range }
const joinRangeEndIdxExclusive = findLastIndex(this._normalizedRanges, r => r.startLineNumber <= range.endLineNumberExclusive) + 1;
if (joinRangeStartIdx === joinRangeEndIdxExclusive) {
return [range];
}
const result: LineRange[] = [];
let startLineNumber = range.startLineNumber;
for (let i = joinRangeStartIdx; i < joinRangeEndIdxExclusive; i++) {
const r = this._normalizedRanges[i];
if (r.startLineNumber > startLineNumber) {
result.push(new LineRange(startLineNumber, r.startLineNumber));
}
startLineNumber = r.endLineNumberExclusive;
}
if (startLineNumber < range.endLineNumberExclusive) {
result.push(new LineRange(startLineNumber, range.endLineNumberExclusive));
}
return result;
}
}
function mapMinusOne(idx: number, mapTo: number): number {
return idx === -1 ? mapTo : idx;
}
function coverFullWords(sequence1: LinesSliceCharSequence, sequence2: LinesSliceCharSequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] {
const additional: SequenceDiff[] = [];
@@ -676,6 +874,7 @@ class LineRangeFragment {
constructor(
public readonly range: LineRange,
public readonly lines: string[],
public readonly source: LineRangeMapping,
) {
let counter = 0;
for (let i = range.startLineNumber - 1; i < range.endLineNumberExclusive - 1; i++) {
@@ -1039,7 +1039,7 @@ class StandaloneContextMenuService extends ContextMenuService {
class StandaloneAudioService implements IAudioCueService {
_serviceBrand: undefined;
async playAudioCue(cue: AudioCue, allowManyInParallel?: boolean | undefined): Promise<void> {
async playAudioCue(cue: AudioCue, options: {}): Promise<void> {
}
async playAudioCues(cues: AudioCue[]): Promise<void> {
@@ -11,13 +11,14 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { Event } from 'vs/base/common/event';
import { localize } from 'vs/nls';
import { observableFromEvent, derived } from 'vs/base/common/observable';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export const IAudioCueService = createDecorator<IAudioCueService>('audioCue');
export interface IAudioCueService {
readonly _serviceBrand: undefined;
playAudioCue(cue: AudioCue, allowManyInParallel?: boolean): Promise<void>;
playAudioCues(cues: AudioCue[]): Promise<void>;
playAudioCue(cue: AudioCue, options?: IAudioCueOptions): Promise<void>;
playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise<void>;
isEnabled(cue: AudioCue): boolean;
onEnabledChanged(cue: AudioCue): Event<void>;
@@ -25,33 +26,72 @@ export interface IAudioCueService {
playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable;
}
export interface IAudioCueOptions {
allowManyInParallel?: boolean;
source?: string;
}
export class AudioCueService extends Disposable implements IAudioCueService {
readonly _serviceBrand: undefined;
sounds: Map<string, HTMLAudioElement> = new Map();
private readonly sounds: Map<string, HTMLAudioElement> = new Map();
private readonly screenReaderAttached = observableFromEvent(
this.accessibilityService.onDidChangeScreenReaderOptimized,
() => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized()
);
private readonly sentTelemetry = new Set<string>();
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
super();
}
public async playAudioCue(cue: AudioCue, allowManyInParallel = false): Promise<void> {
public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise<void> {
if (this.isEnabled(cue)) {
await this.playSound(cue.sound.getSound(), allowManyInParallel);
this.sendAudioCueTelemetry(cue, options.source);
await this.playSound(cue.sound.getSound(), options.allowManyInParallel);
}
}
public async playAudioCues(cues: AudioCue[]): Promise<void> {
public async playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise<void> {
for (const cue of cues) {
this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined);
}
// Some audio cues might reuse sounds. Don't play the same sound twice.
const sounds = new Set(cues.filter(cue => this.isEnabled(cue)).map(cue => cue.sound.getSound()));
const sounds = new Set(cues.map(c => 'cue' in c ? c.cue : c).filter(cue => this.isEnabled(cue)).map(cue => cue.sound.getSound()));
await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true)));
}
private sendAudioCueTelemetry(cue: AudioCue, source: string | undefined): void {
const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized();
const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : '');
// Only send once per user session
if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) {
return;
}
this.sentTelemetry.add(key);
this.telemetryService.publicLog2<{
audioCue: string;
source: string;
isScreenReaderOptimized: boolean;
}, {
owner: 'hediet';
audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' };
source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' };
isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' };
comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.';
}>('audioCue.played', {
audioCue: cue.name,
source: source ?? '',
isScreenReaderOptimized,
});
}
private getVolumeInPercent(): number {
const volume = this.configurationService.getValue<number>('audioCues.volume');
@@ -92,7 +132,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
let playing = true;
const playSound = () => {
if (playing) {
this.playAudioCue(cue, true).finally(() => {
this.playAudioCue(cue, { allowManyInParallel: true }).finally(() => {
setTimeout(() => {
if (playing) {
playSound();
@@ -236,6 +236,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
@memoize
get disableWorkspaceTrust(): boolean { return !!this.args['disable-workspace-trust']; }
@memoize
get disableKeytar(): boolean { return !!this.args['disable-keytar']; }
@memoize
get policyFile(): URI | undefined {
if (this.args['__enable-file-policy']) {
@@ -59,9 +59,6 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
@memoize
get disableUpdates(): boolean { return !!this.args['disable-updates']; }
@memoize
get disableKeytar(): boolean { return !!this.args['disable-keytar']; }
@memoize
get crossOriginIsolated(): boolean { return !!this.args['enable-coi']; }
+22 -13
View File
@@ -10,6 +10,7 @@ import { IStorageService, InMemoryStorageService, StorageScope, StorageTarget }
import { Emitter, Event } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Lazy } from 'vs/base/common/lazy';
export const ISecretStorageService = createDecorator<ISecretStorageService>('secretStorageService');
@@ -39,28 +40,24 @@ export class BaseSecretStorageService implements ISecretStorageService {
private readonly _onDidChangeValueDisposable = new DisposableStore();
protected resolvedStorageService = this.initialize();
constructor(
private readonly _useInMemoryStorage: boolean,
@IStorageService private _storageService: IStorageService,
@IEncryptionService protected _encryptionService: IEncryptionService,
@ILogService protected readonly _logService: ILogService
@ILogService protected readonly _logService: ILogService,
) { }
/**
* @Note initialize must be called first so that this can be resolved properly
* otherwise it will return 'unknown'.
*/
get type() {
return this._type;
}
private onDidChangeValue(key: string): void {
if (!key.startsWith(this._storagePrefix)) {
return;
}
const secretKey = key.slice(this._storagePrefix.length);
this._logService.trace(`[SecretStorageService] Notifying change in value for secret: ${secretKey}`);
this.onDidChangeSecretEmitter.fire(secretKey);
private _lazyStorageService: Lazy<Promise<IStorageService>> = new Lazy(() => this.initialize());
protected get resolvedStorageService() {
return this._lazyStorageService.value;
}
get(key: string): Promise<string | undefined> {
@@ -127,6 +124,7 @@ export class BaseSecretStorageService implements ISecretStorageService {
private async initialize(): Promise<IStorageService> {
let storageService;
if (!this._useInMemoryStorage && await this._encryptionService.isEncryptionAvailable()) {
this._logService.trace(`[SecretStorageService] Encryption is available, using persisted storage`);
this._type = 'persisted';
storageService = this._storageService;
} else {
@@ -147,7 +145,18 @@ export class BaseSecretStorageService implements ISecretStorageService {
}
protected reinitialize(): void {
this.resolvedStorageService = this.initialize();
this._lazyStorageService = new Lazy(() => this.initialize());
}
private onDidChangeValue(key: string): void {
if (!key.startsWith(this._storagePrefix)) {
return;
}
const secretKey = key.slice(this._storagePrefix.length);
this._logService.trace(`[SecretStorageService] Notifying change in value for secret: ${secretKey}`);
this.onDidChangeSecretEmitter.fire(secretKey);
}
private getKey(key: string): string {
@@ -5,7 +5,6 @@
import * as assert from 'assert';
import * as sinon from 'sinon';
import { timeout } from 'vs/base/common/async';
import { IEncryptionService, KnownStorageProvider } from 'vs/platform/encryption/common/encryptionService';
import { NullLogService } from 'vs/platform/log/common/log';
import { BaseSecretStorageService } from 'vs/platform/secrets/common/secrets';
@@ -67,8 +66,10 @@ suite('secrets', () => {
});
test('type', async () => {
// allow initialization to complete
await timeout(0);
assert.strictEqual(service.type, 'unknown');
// trigger lazy initialization
await service.set('my-secret', 'my-secret-value');
assert.strictEqual(service.type, 'in-memory');
});
@@ -122,8 +123,10 @@ suite('secrets', () => {
});
test('type', async () => {
// allow initialization to complete
await timeout(0);
assert.strictEqual(service.type, 'unknown');
// trigger lazy initialization
await service.set('my-secret', 'my-secret-value');
assert.strictEqual(service.type, 'persisted');
});
@@ -177,8 +180,10 @@ suite('secrets', () => {
});
test('type', async () => {
// allow initialization to complete
await timeout(0);
assert.strictEqual(service.type, 'unknown');
// trigger lazy initialization
await service.set('my-secret', 'my-secret-value');
assert.strictEqual(service.type, 'in-memory');
});
@@ -41,6 +41,8 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { ILogService } from 'vs/platform/log/common/log';
import { ISecretStorageService } from 'vs/platform/secrets/common/secrets';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { runWhenIdle } from 'vs/base/common/async';
export class ViewContainerActivityAction extends ActivityAction {
@@ -239,6 +241,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
colors: (theme: IColorTheme) => ICompositeBarColors,
activityHoverOptions: IActivityHoverOptions,
@IThemeService themeService: IThemeService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IHoverService hoverService: IHoverService,
@IContextMenuService contextMenuService: IContextMenuService,
@IMenuService menuService: IMenuService,
@@ -285,6 +288,16 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
// This function exists to ensure that the accounts are added for auth providers that had already been registered
// before the menu was created.
private async initialize(): Promise<void> {
// Resolving the menu doesn't need to happen immediately, so we can wait until after the workbench has been restored
// and only run this when the system is idle.
await this.lifecycleService.when(LifecyclePhase.Restored);
const disposable = this._register(runWhenIdle(async () => {
await this.doInitialize();
disposable.dispose();
}));
}
private async doInitialize(): Promise<void> {
const providerIds = this.authenticationService.getProviderIds();
const results = await Promise.allSettled(providerIds.map(providerId => this.addAccountsFromProvider(providerId)));
@@ -1499,15 +1499,7 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon
if (focus) {
const selection: Array<IEditorIdentifier | IEditorGroup> = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
// Only respect selection if it contains focused element
if (selection?.some(s => {
if (isEditorGroup(s)) {
return s.id === focus.groupId;
}
const group = editorGroupService.getGroup(s.groupId);
return s.groupId === focus.groupId && (group ? group.getIndexOfEditor(s.editor) : -1) === focus.editorIndex;
})) {
if (selection.length > 0) {
return selection.map(elementToContext);
}
@@ -27,9 +27,11 @@ import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeA
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { marked } from 'vs/base/common/marked/marked';
const enum DIMENSIONS {
MAX_WIDTH = 600
@@ -42,6 +44,10 @@ export interface IAccessibleContentProvider {
onKeyDown?(e: IKeyboardEvent): void;
previous?(): void;
next?(): void;
/**
* When the language is markdown, this is provided by default.
*/
getSymbols?(): IAccessibleViewSymbol[];
options: IAccessibleViewOptions;
}
@@ -52,6 +58,7 @@ export interface IAccessibleViewService {
show(provider: IAccessibleContentProvider): void;
next(): void;
previous(): void;
goToSymbol(): void;
/**
* If the setting is enabled, provides the open accessible view hint as a localized string.
* @param verbositySettingKey The setting key for the verbosity of the feature
@@ -128,15 +135,21 @@ class AccessibleView extends Disposable {
}));
}
show(provider: IAccessibleContentProvider): void {
show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol): void {
if (!provider) {
provider = this._currentProvider;
}
if (!provider) {
return;
}
const delegate: IContextViewDelegate = {
getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; },
render: (container) => {
container.classList.add('accessible-view-container');
return this._render(provider, container);
return this._render(provider!, container);
},
onHide: () => {
if (provider.options.type === AccessibleViewType.Help) {
if (provider!.options.type === AccessibleViewType.Help) {
this._accessiblityHelpIsShown.reset();
} else {
this._accessibleViewIsShown.reset();
@@ -150,6 +163,9 @@ class AccessibleView extends Disposable {
} else {
this._accessibleViewIsShown.set(true);
}
if (symbol && this._currentProvider) {
this.showSymbol(this._currentProvider, symbol);
}
this._currentProvider = provider;
}
@@ -167,6 +183,51 @@ class AccessibleView extends Disposable {
this._currentProvider.next?.();
}
goToSymbol(): void {
this._instantiationService.createInstance(AccessibleViewSymbolQuickPick, this).show(this._currentProvider!);
}
getSymbols(): IAccessibleViewSymbol[] | undefined {
if (!this._currentProvider) {
return;
}
const tokens = this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown' ? this._currentProvider.getSymbols?.() : marked.lexer(this._currentProvider.provideContent());
if (!tokens) {
return;
}
const symbols: IAccessibleViewSymbol[] = [];
for (const token of tokens) {
let label: string | undefined = undefined;
if ('type' in token) {
switch (token.type) {
case 'heading':
case 'paragraph':
case 'code':
label = token.text;
break;
case 'list':
label = token.items?.map(i => i.text).join(', ');
break;
}
} else {
label = token.label;
}
if (label) {
symbols.push({ info: label, label: localize('symbolLabel', "({0}) {1}", token.type, label), ariaLabel: localize('symbolLabelAria', "({0}) {1}", token.type, label) });
}
}
return symbols;
}
showSymbol(provider: IAccessibleContentProvider, symbol: IAccessibleViewSymbol): void {
const index = provider.provideContent().split('\n').findIndex(line => line.includes(symbol.info.split('\n')[0])) ?? -1;
if (index >= 0) {
this.show(provider);
this._editorWidget.revealLine(index + 1);
this._editorWidget.setSelection({ startLineNumber: index + 1, startColumn: 1, endLineNumber: index + 1, endColumn: 1 });
}
}
private _render(provider: IAccessibleContentProvider, container: HTMLElement): IDisposable {
this._currentProvider = provider;
const settingKey = `accessibility.verbosity.${provider.verbositySettingKey}`;
@@ -286,6 +347,9 @@ export class AccessibleViewService extends Disposable implements IAccessibleView
previous(): void {
this._accessibleView?.previous();
}
goToSymbol(): void {
this._accessibleView?.goToSymbol();
}
getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | null {
if (!this._configurationService.getValue(verbositySettingKey)) {
return null;
@@ -300,7 +364,7 @@ class AccessibleViewNextAction extends Action2 {
static id: 'editor.action.accessibleViewNext';
constructor() {
super({
id: AccessibleViewNextAction.id,
id: 'editor.action.accessibleViewNext',
precondition: accessibleViewIsShown,
keybinding: {
primary: KeyMod.Alt | KeyCode.BracketRight,
@@ -318,13 +382,38 @@ class AccessibleViewNextAction extends Action2 {
accessor.get(IAccessibleViewService).next();
}
}
registerAction2(AccessibleViewNextAction);
class AccessibleViewGoToSymbolAction extends Action2 {
static id: 'editor.action.accessibleViewGoToSymbol';
constructor() {
super({
id: 'editor.action.accessibleViewGoToSymbol',
precondition: accessibleViewIsShown,
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO,
weight: KeybindingWeight.WorkbenchContrib + 10
},
menu: [{
id: MenuId.CommandPalette,
group: '',
order: 1
}],
title: localize('editor.action.accessibleViewGoToSymbol', "Go To Symbol in Accessible View")
});
}
run(accessor: ServicesAccessor, ...args: unknown[]): void {
accessor.get(IAccessibleViewService).goToSymbol();
}
}
registerAction2(AccessibleViewGoToSymbolAction);
class AccessibleViewPreviousAction extends Action2 {
static id: 'editor.action.accessibleViewPrevious';
constructor() {
super({
id: AccessibleViewPreviousAction.id,
id: 'editor.action.accessibleViewPrevious',
precondition: accessibleViewIsShown,
keybinding: {
primary: KeyMod.Alt | KeyCode.BracketLeft,
@@ -339,10 +428,10 @@ class AccessibleViewPreviousAction extends Action2 {
});
}
run(accessor: ServicesAccessor, ...args: unknown[]): void {
accessor.get(IAccessibleViewService).next();
accessor.get(IAccessibleViewService).previous();
}
}
registerAction2(AccessibleViewPreviousAction);
function registerCommand<T extends Command>(command: T): T {
command.register();
@@ -388,3 +477,33 @@ export const AccessibleViewAction = registerCommand(new MultiCommand({
}],
}));
class AccessibleViewSymbolQuickPick {
constructor(private _accessibleView: AccessibleView, @IQuickInputService private readonly _quickInputService: IQuickInputService) {
}
show(provider: IAccessibleContentProvider): void {
const quickPick = this._quickInputService.createQuickPick<IAccessibleViewSymbol>();
const picks = [];
const symbols = this._accessibleView.getSymbols();
if (!symbols) {
return;
}
for (const symbol of symbols) {
picks.push({
label: symbol.label,
ariaLabel: symbol.ariaLabel
});
}
quickPick.canSelectMany = false;
quickPick.items = symbols;
quickPick.show();
quickPick.onDidAccept(() => {
this._accessibleView.showSymbol(provider, quickPick.selectedItems[0]);
});
}
}
interface IAccessibleViewSymbol extends IPickerQuickAccessItem {
info: string;
}
@@ -3,12 +3,263 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { AskQuickQuestionAction } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
import 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction';
import 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction';
import * as dom from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/base/common/themables';
import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, IContextKeyService, IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
export const ASK_QUICK_QUESTION_ACTION_ID = 'chat.action.askQuickQuestion';
export function registerChatQuickQuestionActions() {
registerAction2(AskQuickQuestionAction);
}
class AskQuickQuestionAction extends Action2 {
private _currentTimer: any | undefined;
private _input: IQuickPick<IQuickPickItem> | undefined;
private _currentChat: QuickChat | undefined;
constructor() {
super({
id: ASK_QUICK_QUESTION_ACTION_ID,
title: { value: localize('chat', "Chat"), original: 'Chat' },
precondition: CONTEXT_PROVIDER_EXISTS,
icon: Codicon.commentDiscussion,
f1: false,
category: CHAT_CATEGORY,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI,
linux: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI
}
},
menu: {
id: MenuId.LayoutControlMenu,
group: '0_workbench_toggles',
when: ContextKeyExpr.notEquals('config.chat.experimental.defaultMode', 'chatView'),
order: 0
}
});
}
override run(accessor: ServicesAccessor, query: string): void {
const quickInputService = accessor.get(IQuickInputService);
const chatService = accessor.get(IChatService);
const instantiationService = accessor.get(IInstantiationService);
// First things first, clear the existing timer that will dispose the session
clearTimeout(this._currentTimer);
this._currentTimer = undefined;
// If the input is already shown, hide it. This provides a toggle behavior of the quick pick
if (this._input !== undefined) {
this._input.hide();
return;
}
// Check if any providers are available. If not, show nothing
// This shouldn't be needed because of the precondition, but just in case
const providerInfo = chatService.getProviderInfos()[0];
if (!providerInfo) {
return;
}
const disposableStore = new DisposableStore();
//#region Setup quick pick
this._input = quickInputService.createQuickPick();
disposableStore.add(this._input);
this._input.hideInput = true;
const containerSession = dom.$('.interactive-session');
this._input.widget = containerSession;
this._currentChat ??= instantiationService.createInstance(QuickChat, {
providerId: providerInfo.id,
});
// show needs to come before the current chat rendering
this._input.show();
this._currentChat.render(containerSession);
const clearButton = {
iconClass: ThemeIcon.asClassName(Codicon.clearAll),
tooltip: localize('clear', "Clear"),
};
this._input.buttons = [
clearButton,
{
iconClass: ThemeIcon.asClassName(Codicon.commentDiscussion),
tooltip: localize('openInChat', "Open In Chat View"),
}
];
this._input.title = providerInfo.displayName;
disposableStore.add(this._input.onDidHide(() => {
disposableStore.dispose();
this._input = undefined;
this._currentTimer = setTimeout(() => {
this._currentChat?.dispose();
this._currentChat = undefined;
}, 1000 * 30); // 30 seconds
}));
//#endregion
disposableStore.add(this._input.onDidAccept(() => {
this._currentChat?.acceptInput();
}));
disposableStore.add(this._input.onDidTriggerButton((e) => {
if (e === clearButton) {
this._currentChat?.clear();
} else {
this._currentChat?.openChatView();
}
}));
this._currentChat.focus();
if (query) {
this._currentChat.setValue(query);
this._currentChat.acceptInput();
}
}
}
class QuickChat extends Disposable {
private widget!: ChatWidget;
private model: ChatModel | undefined;
private _currentQuery: string | undefined;
private _scopedContextKeyService!: IScopedContextKeyService;
get scopedContextKeyService() {
return this._scopedContextKeyService;
}
constructor(
private readonly _options: IChatViewOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatService private readonly chatService: IChatService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService
) {
super();
}
clear() {
this.model?.dispose();
this.model = undefined;
this.updateModel();
this.widget.inputEditor.setValue('');
}
focus(): void {
if (this.widget) {
this.widget.focusInput();
}
}
render(parent: HTMLElement): void {
this._scopedContextKeyService?.dispose();
this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent));
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]));
this.widget?.dispose();
this.widget = this._register(
scopedInstantiationService.createInstance(
ChatWidget,
{ resource: true, renderInputOnTop: true },
{
listForeground: editorForeground,
listBackground: editorBackground,
inputEditorBackground: SIDE_BAR_BACKGROUND,
resultEditorBackground: SIDE_BAR_BACKGROUND
}));
this.widget.render(parent);
this.widget.setVisible(true);
this.widget.setDynamicChatTreeItemLayout(2, 600);
this.updateModel();
if (this._currentQuery) {
this.widget.inputEditor.setSelection({
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: this._currentQuery.length + 1
});
}
this.registerListeners();
}
private registerListeners(): void {
this._register(this.widget.inputEditor.onDidChangeModelContent((e) => {
this._currentQuery = this.widget.inputEditor.getValue();
}));
}
async acceptInput(): Promise<void> {
if (this.widget.inputEditor.getValue().trim() === '/clear') {
this.clear();
} else {
await this.widget.acceptInput();
}
}
async openChatView(): Promise<void> {
const widget = await this._chatWidgetService.revealViewForProvider(this._options.providerId);
if (!widget?.viewModel || !this.model) {
return;
}
for (const request of this.model.getRequests()) {
if (request.response?.response.value || request.response?.errorDetails) {
this.chatService.addCompleteRequest(widget.viewModel.sessionId,
request.message as string,
{
message: request.response.response.value,
errorDetails: request.response.errorDetails
});
} else if (request.message) {
}
}
const value = this.widget.inputEditor.getValue();
if (value) {
widget.inputEditor.setValue(value);
}
widget.focusInput();
}
setValue(value: string): void {
this.widget.inputEditor.setValue(value);
}
private updateModel(): void {
this.model ??= this.chatService.startSession(this._options.providerId, CancellationToken.None);
if (!this.model) {
throw new Error('Could not start chat session');
}
this.widget.setModel(this.model, { inputValue: this._currentQuery });
}
}
@@ -1,280 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/base/common/themables';
import { localize } from 'vs/nls';
import { IContextKeyService, IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { editorForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { AskQuickQuestionAction, IQuickQuestionMode, QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
interface IChatQuickQuestionModeOptions {
renderInputOnTop: boolean;
useDynamicMessageLayout: boolean;
}
class BaseChatQuickQuestionMode implements IQuickQuestionMode {
private _currentTimer: any | undefined;
private _input: IQuickPick<IQuickPickItem> | undefined;
private _currentChat: QuickChat | undefined;
constructor(
private readonly _options: IChatQuickQuestionModeOptions
) { }
run(accessor: ServicesAccessor, query: string): void {
const quickInputService = accessor.get(IQuickInputService);
const chatService = accessor.get(IChatService);
const instantiationService = accessor.get(IInstantiationService);
// First things first, clear the existing timer that will dispose the session
clearTimeout(this._currentTimer);
this._currentTimer = undefined;
// If the input is already shown, hide it. This provides a toggle behavior of the quick pick
if (this._input !== undefined) {
this._input.hide();
return;
}
// Check if any providers are available. If not, show nothing
// This shouldn't be needed because of the precondition, but just in case
const providerInfo = chatService.getProviderInfos()[0];
if (!providerInfo) {
return;
}
const disposableStore = new DisposableStore();
//#region Setup quick pick
this._input = quickInputService.createQuickPick();
disposableStore.add(this._input);
this._input.hideInput = true;
const containerSession = dom.$('.interactive-session');
this._input.widget = containerSession;
this._currentChat ??= instantiationService.createInstance(QuickChat, {
providerId: providerInfo.id,
...this._options
});
// show needs to come before the current chat rendering
this._input.show();
this._currentChat.render(containerSession);
const clearButton = {
iconClass: ThemeIcon.asClassName(Codicon.clearAll),
tooltip: localize('clear', "Clear"),
};
this._input.buttons = [
clearButton,
{
iconClass: ThemeIcon.asClassName(Codicon.commentDiscussion),
tooltip: localize('openInChat', "Open In Chat View"),
}
];
this._input.title = providerInfo.displayName;
disposableStore.add(this._input.onDidHide(() => {
disposableStore.dispose();
this._input = undefined;
this._currentTimer = setTimeout(() => {
this._currentChat?.dispose();
this._currentChat = undefined;
}, 1000 * 30); // 30 seconds
}));
//#endregion
disposableStore.add(this._input.onDidAccept(() => {
this._currentChat?.acceptInput();
}));
disposableStore.add(this._input.onDidTriggerButton((e) => {
if (e === clearButton) {
this._currentChat?.clear();
} else {
this._currentChat?.openChatView();
}
}));
this._currentChat.layout();
this._currentChat.focus();
if (query) {
this._currentChat.setValue(query);
this._currentChat.acceptInput();
}
}
}
class QuickChat extends Disposable {
private widget!: ChatWidget;
private model: ChatModel | undefined;
private _currentQuery: string | undefined;
private _scopedContextKeyService!: IScopedContextKeyService;
get scopedContextKeyService() {
return this._scopedContextKeyService;
}
private _currentParentElement?: HTMLElement;
constructor(
private readonly _options: IChatViewOptions & IChatQuickQuestionModeOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatService private readonly chatService: IChatService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService
) {
super();
}
clear() {
this.model?.dispose();
this.model = undefined;
this.updateModel();
this.widget.inputEditor.setValue('');
}
focus(): void {
if (this.widget) {
this.widget.focusInput();
}
}
render(parent: HTMLElement): void {
this._currentParentElement = parent;
this._scopedContextKeyService?.dispose();
this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent));
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]));
this.widget?.dispose();
this.widget = this._register(
scopedInstantiationService.createInstance(
ChatWidget,
{ resource: true, renderInputOnTop: this._options.renderInputOnTop },
{
listForeground: editorForeground,
listBackground: editorBackground,
inputEditorBackground: SIDE_BAR_BACKGROUND,
resultEditorBackground: SIDE_BAR_BACKGROUND
}));
this.widget.render(parent);
this.widget.setVisible(true);
if (this._options.useDynamicMessageLayout) {
this.widget.setDynamicChatTreeItemLayout(2, 600);
}
this.updateModel();
if (this._currentQuery) {
this.widget.inputEditor.setSelection({
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: this._currentQuery.length + 1
});
}
this.registerListeners();
}
private registerListeners(): void {
this._register(dom.addDisposableListener(parent, dom.EventType.RESIZE, () => this.layout()));
this._register(this.widget.inputEditor.onDidChangeModelContent((e) => {
this._currentQuery = this.widget.inputEditor.getValue();
}));
}
async acceptInput(): Promise<void> {
if (this.widget.inputEditor.getValue().trim() === '/clear') {
this.clear();
} else {
await this.widget.acceptInput();
}
this.layout();
}
async openChatView(): Promise<void> {
const widget = await this._chatWidgetService.revealViewForProvider(this._options.providerId);
if (!widget?.viewModel || !this.model) {
return;
}
for (const request of this.model.getRequests()) {
if (request.response?.response.value || request.response?.errorDetails) {
this.chatService.addCompleteRequest(widget.viewModel.sessionId,
request.message as string,
{
message: request.response.response.value,
errorDetails: request.response.errorDetails
});
} else if (request.message) {
}
}
const value = this.widget.inputEditor.getValue();
if (value) {
widget.inputEditor.setValue(value);
}
widget.focusInput();
}
setValue(value: string): void {
this.widget.inputEditor.setValue(value);
}
layout(): void {
if (!this._options.useDynamicMessageLayout && this._currentParentElement) {
this.widget.layout(500, this._currentParentElement.offsetWidth);
}
}
private updateModel(): void {
this.model ??= this.chatService.startSession(this._options.providerId, CancellationToken.None);
if (!this.model) {
throw new Error('Could not start chat session');
}
this.widget.setModel(this.model, { inputValue: this._currentQuery });
}
}
AskQuickQuestionAction.registerMode(
QuickQuestionMode.InputOnTopChat,
class InputOnTopChatQuickQuestionMode extends BaseChatQuickQuestionMode {
constructor() {
super({
renderInputOnTop: true,
useDynamicMessageLayout: true
});
}
}
);
AskQuickQuestionAction.registerMode(
QuickQuestionMode.InputOnBottomChat,
class InputOnBottomChatQuickQuestionMode extends BaseChatQuickQuestionMode {
constructor() {
super({
renderInputOnTop: false,
useDynamicMessageLayout: false
});
}
}
);
@@ -1,71 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Lazy } from 'vs/base/common/lazy';
import { localize } from 'vs/nls';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
export const ASK_QUICK_QUESTION_ACTION_ID = 'chat.action.askQuickQuestion';
export const enum QuickQuestionMode {
SingleQuestion = 'singleQuestion',
InputOnTopChat = 'inputOnTopChat',
InputOnBottomChat = 'inputOnBottomChat',
}
export interface IQuickQuestionMode {
run(accessor: ServicesAccessor, query: string): void;
}
// TODO: This should be registered per chat-provider probably.
export class AskQuickQuestionAction extends Action2 {
private static readonly modeRegistry: Map<QuickQuestionMode, Lazy<IQuickQuestionMode>> = new Map();
static registerMode(mode: QuickQuestionMode, modeAction: { new(): IQuickQuestionMode }) {
AskQuickQuestionAction.modeRegistry.set(mode, new Lazy(() => new modeAction()));
}
constructor() {
super({
id: ASK_QUICK_QUESTION_ACTION_ID,
title: { value: localize('chat', "Chat"), original: 'Chat' },
precondition: CONTEXT_PROVIDER_EXISTS,
icon: Codicon.commentDiscussion,
f1: false,
category: CHAT_CATEGORY,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI,
linux: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI
}
},
menu: {
id: MenuId.LayoutControlMenu,
group: '0_workbench_toggles',
when: ContextKeyExpr.notEquals('config.chat.experimental.defaultMode', 'chatView'),
order: 0
}
});
}
override run(accessor: ServicesAccessor, query: string): void {
const configurationService = accessor.get(IConfigurationService);
const mode = configurationService.getValue<QuickQuestionMode>('chat.experimental.quickQuestion.mode');
const modeAction = AskQuickQuestionAction.modeRegistry.get(mode);
if (modeAction) {
modeAction.value.run(accessor, query);
}
}
}
@@ -1,261 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { asCssVariable, editorBackground, foreground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry';
import { ChatListItemRenderer } from 'vs/workbench/contrib/chat/browser/chatListRenderer';
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { AskQuickQuestionAction, IQuickQuestionMode, QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
class AskSingleQuickQuestionMode implements IQuickQuestionMode {
private _currentSession: InteractiveQuickPickSession | undefined;
private _currentQuery: string | undefined;
private _lastAcceptedQuery: string | undefined;
private _currentTimer: any | undefined;
private _input: IQuickPick<IQuickPickItem> | undefined;
run(accessor: ServicesAccessor, query: string): void {
const quickInputService = accessor.get(IQuickInputService);
const chatService = accessor.get(IChatService);
const instantiationService = accessor.get(IInstantiationService);
// First things first, clear the existing timer that will dispose the session
clearTimeout(this._currentTimer);
this._currentTimer = undefined;
// If the input is already shown, hide it. This provides a toggle behavior of the quick pick
if (this._input !== undefined) {
this._input.hide();
return;
}
// Check if any providers are available. If not, show nothing
const providerInfo = chatService.getProviderInfos()[0];
if (!providerInfo) {
return;
}
const disposableStore = new DisposableStore();
//#region Setup quick pick
this._input = quickInputService.createQuickPick();
disposableStore.add(this._input);
this._input.placeholder = localize('askabot', "Ask {0} a question...", providerInfo.displayName);
// Setup toggle that will be used to open the chat view
const openInChat = new Toggle({
title: 'Open In Chat View',
icon: Codicon.commentDiscussion,
isChecked: false,
inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder),
inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground),
inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground)
});
disposableStore.add(openInChat);
disposableStore.add(openInChat.onChange(async () => {
await this._currentSession?.openInChat(this._lastAcceptedQuery ?? this._input!.value);
this._currentQuery = undefined;
this._lastAcceptedQuery = undefined;
this._currentSession?.dispose();
this._currentSession = undefined;
}));
this._input.toggles = [openInChat];
// Setup the widget that will be used to render the chat response
const containerList = dom.$('.interactive-list');
const containerSession = dom.$('.interactive-session', undefined, containerList);
containerList.style.position = 'relative';
this._input.widget = containerSession;
//#endregion
//#region quick pick events
disposableStore.add(this._input.onDidChangeValue((value) => {
if (value !== this._currentQuery) {
this._currentQuery = value;
}
}));
disposableStore.add(this._input.onDidHide(() => {
disposableStore.dispose();
this._input = undefined;
this._currentTimer = setTimeout(() => {
this._currentQuery = undefined;
this._lastAcceptedQuery = undefined;
this._currentSession?.dispose();
this._currentSession = undefined;
}, 1000 * 30); // 30 seconds
}));
disposableStore.add(this._input.onDidAccept(async () => {
await this._currentSession?.accept(this._input!.value);
this._lastAcceptedQuery = this._input!.value;
}));
//#endregion
// If we were given a query (via executeCommand), then clear past state
if (query) {
this._currentSession?.dispose();
this._currentSession = undefined;
}
this._currentSession ??= instantiationService.createInstance(InteractiveQuickPickSession);
this._input.show();
// This line must come after showing the input so the offsetWidth is correct
this._currentSession.createList(containerList, containerList.offsetWidth);
disposableStore.add(this._currentSession.onDidClickFollowup(async e => {
this._input!.focusOnInput();
this._input!.value = e.message;
await this._currentSession?.accept(e.message);
}));
// If we were given a query (via executeCommand), then accept it
if (query) {
this._input.value = query;
this._input.valueSelection = [0, this._input.value.length];
this._currentQuery = query;
this._currentSession.accept(query);
} else if (this._currentQuery) {
this._input.value = this._currentQuery;
this._input.valueSelection = [0, this._input.value.length];
}
}
}
class InteractiveQuickPickSession extends Disposable {
private _model: ChatModel;
private _viewModel: ChatViewModel;
private readonly _onDidClickFollowup: Emitter<IChatReplyFollowup> = this._register(new Emitter<IChatReplyFollowup>());
onDidClickFollowup: Event<IChatReplyFollowup> = this._onDidClickFollowup.event;
private _listDisposable: DisposableStore | undefined;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IChatService private readonly _chatService: IChatService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService
) {
super();
const providerInfo = _chatService.getProviderInfos()[0];
this._model = this._register(_chatService.startSession(providerInfo.id, CancellationToken.None)!);
this._viewModel = this._register(new ChatViewModel(this._model, _instantiationService));
}
createList(container: HTMLElement, offsetWidth: number) {
this._listDisposable?.dispose();
this._listDisposable = new DisposableStore();
const options = this._listDisposable.add(this._instantiationService.createInstance(ChatEditorOptions, 'quickpick-interactive', foreground, editorBackground, editorBackground));
const list = this._listDisposable.add(this._instantiationService.createInstance(
ChatListItemRenderer,
options,
{
getListLength: () => {
return 1;
},
getSlashCommands() {
return [];
},
}
));
const template = list.renderTemplate(container);
list.layout(offsetWidth);
this._listDisposable.add(this._viewModel.onDidChange(() => {
const items = this._viewModel.getItems();
const node = {
element: items[items.length - 1],
children: [],
collapsed: false,
collapsible: false,
depth: 0,
filterData: undefined,
visible: true,
visibleChildIndex: 0,
visibleChildrenCount: 1,
};
list.disposeElement(node, 0, template);
list.renderElement(node, 0, template);
}));
if (this._viewModel.getItems().length) {
const items = this._viewModel.getItems();
const node = {
element: items[items.length - 1],
children: [],
collapsed: false,
collapsible: false,
depth: 0,
filterData: undefined,
visible: true,
visibleChildIndex: 0,
visibleChildrenCount: 1,
};
list.disposeElement(node, 0, template);
list.renderElement(node, 0, template);
}
this._listDisposable.add(list.onDidClickFollowup(e => {
this._onDidClickFollowup.fire(e);
}));
}
get providerId() {
return this._model.providerId;
}
get sessionId() {
return this._model.sessionId;
}
async accept(query: string) {
await this._model.waitForInitialization();
const requests = this._model.getRequests();
const lastRequest = requests[requests.length - 1];
if (lastRequest?.message && lastRequest?.message === query) {
return;
}
if (this._model.requestInProgress) {
this._chatService.cancelCurrentRequestForSession(this.sessionId);
}
await this._chatService.sendRequest(this.sessionId, query);
}
async openInChat(value: string) {
const widget = await this._chatWidgetService.revealViewForProvider(this.providerId);
if (!widget?.viewModel) {
return;
}
const requests = this._model.getRequests().reverse();
const response = requests.find(r => r.response?.response.value !== undefined);
const message = response?.response?.response.value;
if (message) {
this._chatService.addCompleteRequest(widget.viewModel.sessionId, value, { message });
} else if (value) {
this._chatService.sendRequest(widget.viewModel.sessionId, value);
}
widget.focusInput();
}
}
AskQuickQuestionAction.registerMode(QuickQuestionMode.SingleQuestion, AskSingleQuickQuestionMode);
@@ -42,9 +42,10 @@ import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibility.contribution';
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IMarkdownString } from 'vs/base/common/htmlContent';
// Register configuration
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@@ -90,13 +91,6 @@ configurationRegistry.registerConfiguration({
],
description: nls.localize('interactiveSession.defaultMode', "Controls the default mode of the chat experience."),
default: 'chatView'
},
'chat.experimental.quickQuestion.mode': {
type: 'string',
tags: ['experimental'],
enum: [QuickQuestionMode.SingleQuestion, QuickQuestionMode.InputOnTopChat, QuickQuestionMode.InputOnBottomChat],
description: nls.localize('interactiveSession.quickQuestion.mode', "Controls the mode of quick question chat experience."),
default: QuickQuestionMode.InputOnTopChat,
}
}
});
@@ -171,8 +165,19 @@ class ChatAccessibleViewContribution extends Disposable {
}
widget.focus(focusedItem);
const responseContent = isResponseVM(focusedItem) ? focusedItem.response.value : undefined;
const isWelcome = focusedItem instanceof ChatWelcomeMessageModel;
let responseContent = isResponseVM(focusedItem) ? focusedItem.response.value : undefined;
if (isWelcome) {
const welcomeReplyContents = [];
for (const content of focusedItem.content) {
if (Array.isArray(content)) {
welcomeReplyContents.push(...content.map(m => m.message));
} else {
welcomeReplyContents.push((content as IMarkdownString).value);
}
}
responseContent = welcomeReplyContents.join('\n');
}
if (!responseContent) {
return false;
}
@@ -182,7 +187,7 @@ class ChatAccessibleViewContribution extends Disposable {
accessibleViewService.show({
verbositySettingKey: AccessibilityVerbositySettingId.Chat,
provideContent(): string { return responseContent; },
provideContent(): string { return responseContent!; },
onClose() {
verifiedWidget.reveal(focusedItem);
if (chatInputFocused) {
@@ -30,7 +30,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi
}, CHAT_RESPONSE_PENDING_ALLOWANCE_MS));
}
acceptRequest(): void {
this._audioCueService.playAudioCue(AudioCue.chatRequestSent, true);
this._audioCueService.playAudioCue(AudioCue.chatRequestSent, { allowManyInParallel: true });
this._runOnceScheduler.schedule();
}
acceptResponse(response?: IChatResponseViewModel | string): void {
@@ -42,7 +42,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi
if (this._lastResponse === responseContent) {
return;
}
this._audioCueService.playAudioCue(AudioCue.chatResponseReceived, true);
this._audioCueService.playAudioCue(AudioCue.chatResponseReceived, { allowManyInParallel: true });
this._hasReceivedRequest = false;
if (!response) {
return;
@@ -555,6 +555,17 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider<Cha
private _getLabelWithCodeBlockCount(element: IChatResponseViewModel): string {
const accessibleViewHint = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.Chat);
let label: string = '';
let commandFollowUpInfo;
const commandFollowupLength = element.commandFollowups?.length ?? 0;
switch (commandFollowupLength) {
case 0:
break;
case 1:
commandFollowUpInfo = localize('commandFollowUpInfo', "Command: {0}", element.commandFollowups![0].title);
break;
default:
commandFollowUpInfo = localize('commandFollowUpInfoMany', "Commands: {0}", element.commandFollowups!.map(followup => followup.title).join(', '));
}
const codeBlockCount = marked.lexer(element.response.value).filter(token => token.type === 'code')?.length ?? 0;
switch (codeBlockCount) {
case 0:
@@ -567,7 +578,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider<Cha
label = accessibleViewHint ? localize('multiCodeBlockHint', "{0} code blocks: {1}", codeBlockCount, element.response.value, accessibleViewHint) : localize('multiCodeBlock', "{0} code blocks", codeBlockCount, element.response.value);
break;
}
return label;
return commandFollowUpInfo ? commandFollowUpInfo + ', ' + label : label;
}
}
@@ -35,7 +35,7 @@ import { isFirefox } from 'vs/base/browser/browser';
import { IProductService } from 'vs/platform/product/common/productService';
import { ISemanticSimilarityService } from 'vs/workbench/services/semanticSimilarity/common/semanticSimilarityService';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions';
export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider {
@@ -662,13 +662,13 @@ async function playAudioCueForChange(change: IChange, audioCueService: IAudioCue
const changeType = getChangeType(change);
switch (changeType) {
case ChangeType.Add:
audioCueService.playAudioCue(AudioCue.diffLineInserted, true);
audioCueService.playAudioCue(AudioCue.diffLineInserted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' });
break;
case ChangeType.Delete:
audioCueService.playAudioCue(AudioCue.diffLineDeleted, true);
audioCueService.playAudioCue(AudioCue.diffLineDeleted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' });
break;
case ChangeType.Modify:
audioCueService.playAudioCue(AudioCue.diffLineModified, true);
audioCueService.playAudioCue(AudioCue.diffLineModified, { allowManyInParallel: true, source: 'dirtyDiffDecoration' });
break;
}
}
@@ -78,7 +78,7 @@ export class TerminalAccessibilityHelpContribution extends Disposable {
return;
}
accessibleViewService.show(instantiationService.createInstance(TerminalAccessibleContentProvider, instance, terminal));
}, TerminalContextKeys.focus));
}, ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.accessibleBufferFocus)));
}
}
registerTerminalContribution(TerminalAccessibilityHelpContribution.ID, TerminalAccessibilityHelpContribution);
@@ -7,6 +7,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { format } from 'vs/base/common/strings';
import { localize } from 'vs/nls';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
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';
@@ -14,6 +16,7 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil
import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
import type { Terminal } from 'xterm';
export const enum ClassName {
@@ -27,7 +30,11 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc
private readonly _hasShellIntegration: boolean = false;
onClose() {
this._instance.focus();
if (this._contextKeyService.getContextKeyValue<boolean>(TerminalContextKeys.accessibleBufferFocus.key) === true) {
this._commandService.executeCommand(TerminalCommandId.FocusAccessibleBuffer);
} else {
this._instance.focus();
}
this.dispose();
}
options: IAccessibleViewOptions = {
@@ -42,7 +49,9 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc
_xterm: Pick<IXtermTerminal, 'getFont' | 'shellIntegration'> & { raw: Terminal },
@IInstantiationService _instantiationService: IInstantiationService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@ICommandService private readonly _commandService: ICommandService
) {
super();
this._hasShellIntegration = _xterm.shellIntegration.status === ShellIntegrationStatus.VSCode;
@@ -147,7 +147,7 @@ export class AccessibleBufferWidget extends TerminalAccessibleWidget {
return;
}
if (activeItem.exitCode) {
this._audioCueService.playAudioCue(AudioCue.error, true);
this._audioCueService.playAudioCue(AudioCue.error, { allowManyInParallel: true, source: 'accessibleBufferWidget' });
}
this.editorWidget.revealLine(activeItem.lineNumber, 0);
});
@@ -489,9 +489,11 @@ class ResultSummaryView extends Disposable {
const { results } = this.resultService;
const { count, root, status, duration, rerun } = this.elements;
if (!results.length) {
this.container.removeChild(root);
this.container.innerText = localize('noResults', 'No test results yet.');
this.elementsWereAttached = false;
if (this.elementsWereAttached) {
this.container.removeChild(root);
this.elementsWereAttached = false;
}
return;
}
@@ -255,7 +255,9 @@ export class EditorResolverService extends Disposable implements IEditorResolver
getAssociationsForResource(resource: URI): EditorAssociations {
const associations = this.getAllUserAssociations();
const matchingAssociations = associations.filter(association => association.filenamePattern && globMatchesResource(association.filenamePattern, resource));
let matchingAssociations = associations.filter(association => association.filenamePattern && globMatchesResource(association.filenamePattern, resource));
// Sort matching associations based on glob length as a longer glob will be more specific
matchingAssociations = matchingAssociations.sort((a, b) => (b.filenamePattern?.length ?? 0) - (a.filenamePattern?.length ?? 0));
const allEditors: RegisteredEditors = this._registeredEditors;
// Ensure that the settings are valid editors
return matchingAssociations.filter(association => allEditors.find(c => c.editorInfo.id === association.viewType));
@@ -43,7 +43,7 @@ export class NativeSecretStorageService extends BaseSecretStorageService {
this._sequencer.queue(key, async () => {
await this.resolvedStorageService;
if (this.type !== 'persisted') {
if (this.type !== 'persisted' && !this._environmentService.disableKeytar) {
this._logService.trace('[NativeSecretStorageService] Notifying user that secrets are not being stored on disk.');
await this.notifyOfNoEncryptionOnce();
}
+1 -1
View File
@@ -15980,7 +15980,7 @@ declare module 'vscode' {
* @param id The unique identifier of the provider.
* @param label The human-readable name of the provider.
* @param provider The authentication provider provider.
* @params options Additional options for the provider.
* @param options Additional options for the provider.
* @return A {@link Disposable} that unregisters this provider when being disposed.
*/
export function registerAuthenticationProvider(id: string, label: string, provider: AuthenticationProvider, options?: AuthenticationProviderOptions): Disposable;