Compare commits
6 Commits
inline-ass
...
fix-linux-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccb14bb926 | ||
|
|
1e4d80a21f | ||
|
|
f90d9d26a5 | ||
|
|
40a611bf34 | ||
|
|
8ad3a150c8 | ||
|
|
87976e91cf |
88
Cargo.lock
generated
88
Cargo.lock
generated
@@ -2130,30 +2130,15 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec 0.8.0",
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
@@ -2332,9 +2317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "borrow-or-share"
|
||||
version = "0.2.2"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
|
||||
checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
@@ -6008,22 +5993,11 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.13.0"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2"
|
||||
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
|
||||
dependencies = [
|
||||
"bit-set 0.5.3",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||
dependencies = [
|
||||
"bit-set 0.8.0",
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
@@ -6245,9 +6219,9 @@ checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
|
||||
|
||||
[[package]]
|
||||
name = "fluent-uri"
|
||||
version = "0.3.2"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5"
|
||||
checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e"
|
||||
dependencies = [
|
||||
"borrow-or-share",
|
||||
"ref-cast",
|
||||
@@ -7543,6 +7517,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.4"
|
||||
@@ -8632,21 +8617,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "0.30.0"
|
||||
version = "0.37.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d"
|
||||
checksum = "73c9ffb2b5c56d58030e1b532d8e8389da94590515f118cf35b5cb68e4764a7e"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"base64 0.22.1",
|
||||
"bytecount",
|
||||
"data-encoding",
|
||||
"email_address",
|
||||
"fancy-regex 0.14.0",
|
||||
"fancy-regex",
|
||||
"fraction",
|
||||
"getrandom 0.3.4",
|
||||
"idna",
|
||||
"itoa",
|
||||
"num-cmp",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"referencing",
|
||||
"regex",
|
||||
@@ -8654,6 +8639,7 @@ dependencies = [
|
||||
"reqwest 0.12.24",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"unicode-general-category",
|
||||
"uuid-simd",
|
||||
]
|
||||
|
||||
@@ -10202,7 +10188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set 0.8.0",
|
||||
"bit-set",
|
||||
"bitflags 2.9.4",
|
||||
"cfg_aliases 0.2.1",
|
||||
"codespan-reporting 0.12.0",
|
||||
@@ -13058,7 +13044,7 @@ dependencies = [
|
||||
"dap",
|
||||
"dap_adapters",
|
||||
"extension",
|
||||
"fancy-regex 0.14.0",
|
||||
"fancy-regex",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
@@ -13929,13 +13915,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.30.0"
|
||||
version = "0.37.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e"
|
||||
checksum = "4283168a506f0dcbdce31c9f9cce3129c924da4c6bca46e46707fcb746d2d70c"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"fluent-uri",
|
||||
"once_cell",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"serde_json",
|
||||
@@ -17129,7 +17116,7 @@ dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"collections",
|
||||
"fancy-regex 0.14.0",
|
||||
"fancy-regex",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
@@ -17363,12 +17350,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tiktoken-rs"
|
||||
version = "0.9.1"
|
||||
source = "git+https://github.com/zed-industries/tiktoken-rs?rev=7249f999c5fdf9bf3cc5c288c964454e4dac0c00#7249f999c5fdf9bf3cc5c288c964454e4dac0c00"
|
||||
source = "git+https://github.com/zed-industries/tiktoken-rs?rev=2570c4387a8505fb8f1d3f3557454b474f1e8271#2570c4387a8505fb8f1d3f3557454b474f1e8271"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"bstr",
|
||||
"fancy-regex 0.13.0",
|
||||
"fancy-regex",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"rustc-hash 1.1.0",
|
||||
@@ -18500,6 +18487,12 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-general-category"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
@@ -18734,7 +18727,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8"
|
||||
dependencies = [
|
||||
"outref",
|
||||
"uuid",
|
||||
"vsimd",
|
||||
]
|
||||
|
||||
|
||||
@@ -505,7 +505,7 @@ ec4rs = "1.1"
|
||||
emojis = "0.6.1"
|
||||
env_logger = "0.11"
|
||||
exec = "0.3.1"
|
||||
fancy-regex = "0.14.0"
|
||||
fancy-regex = "0.16.0"
|
||||
fork = "0.4.0"
|
||||
futures = "0.3"
|
||||
futures-batch = "0.6.1"
|
||||
@@ -531,7 +531,7 @@ indoc = "2"
|
||||
inventory = "0.3.19"
|
||||
itertools = "0.14.0"
|
||||
json_dotpath = "1.1"
|
||||
jsonschema = "0.30.0"
|
||||
jsonschema = "0.37.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = "0.10.0"
|
||||
jupyter-websocket-client = "0.15.0"
|
||||
@@ -658,7 +658,7 @@ sysinfo = "0.37.0"
|
||||
take-until = "0.2.0"
|
||||
tempfile = "3.20.0"
|
||||
thiserror = "2.0.12"
|
||||
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "7249f999c5fdf9bf3cc5c288c964454e4dac0c00" }
|
||||
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "2570c4387a8505fb8f1d3f3557454b474f1e8271" }
|
||||
time = { version = "0.3", features = [
|
||||
"macros",
|
||||
"parsing",
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
{{#if language_name}}
|
||||
Here's a file of {{language_name}} that the user is going to ask you to make an edit to.
|
||||
{{else}}
|
||||
Here's a file of text that the user is going to ask you to make an edit to.
|
||||
{{/if}}
|
||||
|
||||
The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.
|
||||
|
||||
<document>
|
||||
{{{document_content}}}
|
||||
</document>
|
||||
|
||||
{{#if is_truncated}}
|
||||
The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.
|
||||
{{/if}}
|
||||
|
||||
{{#if rewrite_section}}
|
||||
And here's the section to rewrite based on that prompt again for reference:
|
||||
|
||||
<rewrite_this>
|
||||
{{{rewrite_section}}}
|
||||
</rewrite_this>
|
||||
|
||||
{{#if diagnostic_errors}}
|
||||
Below are the diagnostic errors visible to the user. If the user requests problems to be fixed, use this information, but do not try to fix these errors if the user hasn't asked you to.
|
||||
|
||||
{{#each diagnostic_errors}}
|
||||
<diagnostic_error>
|
||||
<line_number>{{line_number}}</line_number>
|
||||
<error_message>{{error_message}}</error_message>
|
||||
<code_content>{{code_content}}</code_content>
|
||||
</diagnostic_error>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {{content_type}} will be preserved.
|
||||
|
||||
Start at the indentation level in the original file in the rewritten {{content_type}}.
|
||||
|
||||
You must use one of the provided tools to make the rewrite or to provide an explanation as to why the user's request cannot be fulfilled.
|
||||
@@ -1100,13 +1100,22 @@
|
||||
"preview_tabs": {
|
||||
// Whether preview tabs should be enabled.
|
||||
// Preview tabs allow you to open files in preview mode, where they close automatically
|
||||
// when you switch to another file unless you explicitly pin them.
|
||||
// when you open another preview tab.
|
||||
// This is useful for quickly viewing files without cluttering your workspace.
|
||||
"enabled": true,
|
||||
// Whether to open tabs in preview mode when opened from the project panel with a single click.
|
||||
"enable_preview_from_project_panel": true,
|
||||
// Whether to open tabs in preview mode when selected from the file finder.
|
||||
"enable_preview_from_file_finder": false,
|
||||
// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
|
||||
"enable_preview_from_code_navigation": false
|
||||
// Whether to open tabs in preview mode when opened from a multibuffer.
|
||||
"enable_preview_from_multibuffer": true,
|
||||
// Whether to open tabs in preview mode when code navigation is used to open a multibuffer.
|
||||
"enable_preview_multibuffer_from_code_navigation": false,
|
||||
// Whether to open tabs in preview mode when code navigation is used to open a single file.
|
||||
"enable_preview_file_from_code_navigation": true,
|
||||
// Whether to keep tabs in preview mode when code navigation is used to navigate away from them.
|
||||
// If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.
|
||||
"enable_keep_preview_on_code_navigation": false
|
||||
},
|
||||
// Settings related to the file finder.
|
||||
"file_finder": {
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"tab.inactive_background": "#1f2127ff",
|
||||
"tab.active_background": "#0d1016ff",
|
||||
"search.match_background": "#5ac2fe66",
|
||||
"search.active_match_background": "#ea570166",
|
||||
"panel.background": "#1f2127ff",
|
||||
"panel.focused_border": "#5ac1feff",
|
||||
"pane.focused_border": null,
|
||||
@@ -436,6 +437,7 @@
|
||||
"tab.inactive_background": "#ececedff",
|
||||
"tab.active_background": "#fcfcfcff",
|
||||
"search.match_background": "#3b9ee566",
|
||||
"search.active_match_background": "#f88b3666",
|
||||
"panel.background": "#ececedff",
|
||||
"panel.focused_border": "#3b9ee5ff",
|
||||
"pane.focused_border": null,
|
||||
@@ -827,6 +829,7 @@
|
||||
"tab.inactive_background": "#353944ff",
|
||||
"tab.active_background": "#242835ff",
|
||||
"search.match_background": "#73cffe66",
|
||||
"search.active_match_background": "#fd722b66",
|
||||
"panel.background": "#353944ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"tab.inactive_background": "#3a3735ff",
|
||||
"tab.active_background": "#282828ff",
|
||||
"search.match_background": "#83a59866",
|
||||
"search.active_match_background": "#c09f3f66",
|
||||
"panel.background": "#3a3735ff",
|
||||
"panel.focused_border": "#83a598ff",
|
||||
"pane.focused_border": null,
|
||||
@@ -452,6 +453,7 @@
|
||||
"tab.inactive_background": "#393634ff",
|
||||
"tab.active_background": "#1d2021ff",
|
||||
"search.match_background": "#83a59866",
|
||||
"search.active_match_background": "#c9653666",
|
||||
"panel.background": "#393634ff",
|
||||
"panel.focused_border": "#83a598ff",
|
||||
"pane.focused_border": null,
|
||||
@@ -858,6 +860,7 @@
|
||||
"tab.inactive_background": "#3b3735ff",
|
||||
"tab.active_background": "#32302fff",
|
||||
"search.match_background": "#83a59866",
|
||||
"search.active_match_background": "#aea85166",
|
||||
"panel.background": "#3b3735ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -1264,6 +1267,7 @@
|
||||
"tab.inactive_background": "#ecddb4ff",
|
||||
"tab.active_background": "#fbf1c7ff",
|
||||
"search.match_background": "#0b667866",
|
||||
"search.active_match_background": "#ba2d1166",
|
||||
"panel.background": "#ecddb4ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -1670,6 +1674,7 @@
|
||||
"tab.inactive_background": "#ecddb5ff",
|
||||
"tab.active_background": "#f9f5d7ff",
|
||||
"search.match_background": "#0b667866",
|
||||
"search.active_match_background": "#dc351466",
|
||||
"panel.background": "#ecddb5ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -2076,6 +2081,7 @@
|
||||
"tab.inactive_background": "#ecdcb3ff",
|
||||
"tab.active_background": "#f2e5bcff",
|
||||
"search.match_background": "#0b667866",
|
||||
"search.active_match_background": "#d7331466",
|
||||
"panel.background": "#ecdcb3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"tab.inactive_background": "#2f343eff",
|
||||
"tab.active_background": "#282c33ff",
|
||||
"search.match_background": "#74ade866",
|
||||
"search.active_match_background": "#e8af7466",
|
||||
"panel.background": "#2f343eff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -448,6 +449,7 @@
|
||||
"tab.inactive_background": "#ebebecff",
|
||||
"tab.active_background": "#fafafaff",
|
||||
"search.match_background": "#5c79e266",
|
||||
"search.active_match_background": "#d0a92366",
|
||||
"panel.background": "#ebebecff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -4,7 +4,6 @@ mod create_directory_tool;
|
||||
mod delete_path_tool;
|
||||
mod diagnostics_tool;
|
||||
mod edit_file_tool;
|
||||
mod failure_message_tool;
|
||||
mod fetch_tool;
|
||||
mod find_path_tool;
|
||||
mod grep_tool;
|
||||
@@ -13,7 +12,6 @@ mod move_path_tool;
|
||||
mod now_tool;
|
||||
mod open_tool;
|
||||
mod read_file_tool;
|
||||
mod rewrite_section_tool;
|
||||
mod terminal_tool;
|
||||
mod thinking_tool;
|
||||
mod web_search_tool;
|
||||
@@ -27,7 +25,6 @@ pub use create_directory_tool::*;
|
||||
pub use delete_path_tool::*;
|
||||
pub use diagnostics_tool::*;
|
||||
pub use edit_file_tool::*;
|
||||
pub use failure_message_tool::*;
|
||||
pub use fetch_tool::*;
|
||||
pub use find_path_tool::*;
|
||||
pub use grep_tool::*;
|
||||
@@ -36,7 +33,6 @@ pub use move_path_tool::*;
|
||||
pub use now_tool::*;
|
||||
pub use open_tool::*;
|
||||
pub use read_file_tool::*;
|
||||
pub use rewrite_section_tool::*;
|
||||
pub use terminal_tool::*;
|
||||
pub use thinking_tool::*;
|
||||
pub use web_search_tool::*;
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
//! This tool is intended for use with the inline assistant, not the agent panel.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
use gpui::{App, SharedString, Task};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AgentTool, ToolCallEventStream};
|
||||
|
||||
/// Use this tool to provide a message to the user when you're unable to complete a task.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct FailureMessageInput {
|
||||
/// A brief message to the user explaining why you're unable to fulfill the request.
|
||||
///
|
||||
/// The message may use markdown formatting if you wish.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub struct FailureMessageTool;
|
||||
|
||||
impl AgentTool for FailureMessageTool {
|
||||
type Input = FailureMessageInput;
|
||||
type Output = String;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"failure_message"
|
||||
}
|
||||
|
||||
fn kind() -> acp::ToolKind {
|
||||
acp::ToolKind::Think
|
||||
}
|
||||
|
||||
fn initial_title(
|
||||
&self,
|
||||
_input: Result<Self::Input, serde_json::Value>,
|
||||
_cx: &mut App,
|
||||
) -> SharedString {
|
||||
"".into()
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_input: Self::Input,
|
||||
_event_stream: ToolCallEventStream,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
unimplemented!("This function is not used by the inline assistant")
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
//! This tool is intended for use with the inline assistant, not the agent panel.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
use gpui::{App, SharedString, Task};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AgentTool, ToolCallEventStream};
|
||||
|
||||
/// Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct RewriteSectionInput {
|
||||
/// A brief description of the edit you have made.
|
||||
///
|
||||
/// The description may use markdown formatting if you wish.
|
||||
/// This is optional - if the edit is simple or obvious, you should leave it empty.
|
||||
pub description: String,
|
||||
|
||||
/// The text to replace the section with.
|
||||
pub replacement_text: String,
|
||||
}
|
||||
|
||||
pub struct RewriteSectionTool;
|
||||
|
||||
impl AgentTool for RewriteSectionTool {
|
||||
type Input = RewriteSectionInput;
|
||||
type Output = String;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"rewrite_section"
|
||||
}
|
||||
|
||||
fn kind() -> acp::ToolKind {
|
||||
acp::ToolKind::Edit
|
||||
}
|
||||
|
||||
fn initial_title(
|
||||
&self,
|
||||
_input: Result<Self::Input, serde_json::Value>,
|
||||
_cx: &mut App,
|
||||
) -> SharedString {
|
||||
"".into()
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_input: Self::Input,
|
||||
_event_stream: ToolCallEventStream,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
unimplemented!("This function is not used by the inline assistant")
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ impl Render for AgentModelSelector {
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(color)
|
||||
.size(IconSize::Small),
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
use crate::{context::LoadedContext, inline_prompt_editor::CodegenStatus};
|
||||
use agent::{
|
||||
AgentTool as _, FailureMessageInput, FailureMessageTool, RewriteSectionInput,
|
||||
RewriteSectionTool,
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use cloud_llm_client::CompletionIntent;
|
||||
use collections::HashSet;
|
||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||
use feature_flags::{FeatureFlagAppExt as _, InlineAssistantV2FeatureFlag};
|
||||
use futures::{
|
||||
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
||||
channel::mpsc,
|
||||
future::{LocalBoxFuture, Shared},
|
||||
join,
|
||||
};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionError, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelTextStream, Role,
|
||||
report_assistant_event,
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelTextStream, Role, report_assistant_event,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
@@ -40,7 +34,6 @@ use std::{
|
||||
};
|
||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use ui::SharedString;
|
||||
|
||||
pub struct BufferCodegen {
|
||||
alternatives: Vec<Entity<CodegenAlternative>>,
|
||||
@@ -221,10 +214,6 @@ impl BufferCodegen {
|
||||
pub fn last_equal_ranges<'a>(&self, cx: &'a App) -> &'a [Range<Anchor>] {
|
||||
self.active_alternative().read(cx).last_equal_ranges()
|
||||
}
|
||||
|
||||
pub fn model_explanation<'a>(&self, cx: &'a App) -> Option<SharedString> {
|
||||
self.active_alternative().read(cx).model_explanation.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
||||
@@ -249,7 +238,6 @@ pub struct CodegenAlternative {
|
||||
elapsed_time: Option<f64>,
|
||||
completion: Option<String>,
|
||||
pub message_id: Option<String>,
|
||||
pub model_explanation: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
||||
@@ -300,15 +288,14 @@ impl CodegenAlternative {
|
||||
generation: Task::ready(()),
|
||||
diff: Diff::default(),
|
||||
telemetry,
|
||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||
builder,
|
||||
active: active,
|
||||
active,
|
||||
edits: Vec::new(),
|
||||
line_operations: Vec::new(),
|
||||
range,
|
||||
elapsed_time: None,
|
||||
completion: None,
|
||||
model_explanation: None,
|
||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,125 +358,18 @@ impl CodegenAlternative {
|
||||
let api_key = model.api_key(cx);
|
||||
let telemetry_id = model.telemetry_id();
|
||||
let provider_id = model.provider_id();
|
||||
|
||||
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||
let tool_use = cx.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_tool(request.await, cx).await?)
|
||||
});
|
||||
self.handle_tool_use(telemetry_id, provider_id.to_string(), api_key, tool_use, cx);
|
||||
} else {
|
||||
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||
cx.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_text(request.await, cx).await?)
|
||||
})
|
||||
.boxed_local()
|
||||
};
|
||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_request_v2(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
user_prompt: String,
|
||||
context_task: Shared<Task<Option<LoadedContext>>>,
|
||||
cx: &mut App,
|
||||
) -> Result<Task<LanguageModelRequest>> {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let language = buffer.language_at(self.range.start);
|
||||
let language_name = if let Some(language) = language.as_ref() {
|
||||
if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
|
||||
None
|
||||
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
Some(language.name())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let language_name = language_name.as_ref();
|
||||
let start = buffer.point_to_buffer_offset(self.range.start);
|
||||
let end = buffer.point_to_buffer_offset(self.range.end);
|
||||
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
|
||||
let (start_buffer, start_buffer_offset) = start;
|
||||
let (end_buffer, end_buffer_offset) = end;
|
||||
if start_buffer.remote_id() == end_buffer.remote_id() {
|
||||
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
|
||||
} else {
|
||||
anyhow::bail!("invalid transformation range");
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("invalid transformation range");
|
||||
};
|
||||
|
||||
let system_prompt = self
|
||||
.builder
|
||||
.generate_inline_transformation_prompt_v2(
|
||||
language_name,
|
||||
buffer,
|
||||
range.start.0..range.end.0,
|
||||
)
|
||||
.context("generating content prompt")?;
|
||||
|
||||
let temperature = AgentSettings::temperature_for_model(model, cx);
|
||||
|
||||
let tool_input_format = model.tool_input_format();
|
||||
|
||||
Ok(cx.spawn(async move |_cx| {
|
||||
let mut messages = vec![LanguageModelRequestMessage {
|
||||
role: Role::System,
|
||||
content: vec![system_prompt.into()],
|
||||
cache: false,
|
||||
reasoning_details: None,
|
||||
}];
|
||||
|
||||
let mut user_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: Vec::new(),
|
||||
cache: false,
|
||||
reasoning_details: None,
|
||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||
cx.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_text(request.await, cx).await?)
|
||||
})
|
||||
.boxed_local()
|
||||
};
|
||||
|
||||
if let Some(context) = context_task.await {
|
||||
context.add_to_request_message(&mut user_message);
|
||||
}
|
||||
|
||||
user_message.content.push(user_prompt.into());
|
||||
messages.push(user_message);
|
||||
|
||||
let tools = vec![
|
||||
LanguageModelRequestTool {
|
||||
name: RewriteSectionTool::name().to_string(),
|
||||
description: RewriteSectionTool::description().to_string(),
|
||||
input_schema: RewriteSectionTool::input_schema(tool_input_format).to_value(),
|
||||
},
|
||||
LanguageModelRequestTool {
|
||||
name: FailureMessageTool::name().to_string(),
|
||||
description: FailureMessageTool::description().to_string(),
|
||||
input_schema: FailureMessageTool::input_schema(tool_input_format).to_value(),
|
||||
},
|
||||
];
|
||||
|
||||
LanguageModelRequest {
|
||||
thread_id: None,
|
||||
prompt_id: None,
|
||||
intent: Some(CompletionIntent::InlineAssist),
|
||||
mode: None,
|
||||
tools,
|
||||
tool_choice: None,
|
||||
stop: Vec::new(),
|
||||
temperature,
|
||||
messages,
|
||||
thinking_allowed: false,
|
||||
}
|
||||
}))
|
||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
@@ -499,10 +379,6 @@ impl CodegenAlternative {
|
||||
context_task: Shared<Task<Option<LoadedContext>>>,
|
||||
cx: &mut App,
|
||||
) -> Result<Task<LanguageModelRequest>> {
|
||||
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||
return self.build_request_v2(model, user_prompt, context_task, cx);
|
||||
}
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let language = buffer.language_at(self.range.start);
|
||||
let language_name = if let Some(language) = language.as_ref() {
|
||||
@@ -634,7 +510,6 @@ impl CodegenAlternative {
|
||||
|
||||
self.generation = cx.spawn(async move |codegen, cx| {
|
||||
let stream = stream.await;
|
||||
|
||||
let token_usage = stream
|
||||
.as_ref()
|
||||
.ok()
|
||||
@@ -1024,101 +899,6 @@ impl CodegenAlternative {
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_tool_use(
|
||||
&mut self,
|
||||
_telemetry_id: String,
|
||||
_provider_id: String,
|
||||
_api_key: Option<String>,
|
||||
tool_use: impl 'static
|
||||
+ Future<
|
||||
Output = Result<language_model::LanguageModelToolUse, LanguageModelCompletionError>,
|
||||
>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.diff = Diff::default();
|
||||
self.status = CodegenStatus::Pending;
|
||||
|
||||
self.generation = cx.spawn(async move |codegen, cx| {
|
||||
let finish_with_status = |status: CodegenStatus, cx: &mut AsyncApp| {
|
||||
let _ = codegen.update(cx, |this, cx| {
|
||||
this.status = status;
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
});
|
||||
};
|
||||
|
||||
let tool_use = tool_use.await;
|
||||
|
||||
match tool_use {
|
||||
Ok(tool_use) if tool_use.name.as_ref() == "rewrite_section" => {
|
||||
// Parse the input JSON into RewriteSectionInput
|
||||
match serde_json::from_value::<RewriteSectionInput>(tool_use.input) {
|
||||
Ok(input) => {
|
||||
// Store the description if non-empty
|
||||
let description = if !input.description.trim().is_empty() {
|
||||
Some(input.description.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Apply the replacement text to the buffer and compute diff
|
||||
let batch_diff_task = codegen
|
||||
.update(cx, |this, cx| {
|
||||
this.model_explanation = description.map(Into::into);
|
||||
let range = this.range.clone();
|
||||
this.apply_edits(
|
||||
std::iter::once((range, input.replacement_text)),
|
||||
cx,
|
||||
);
|
||||
this.reapply_batch_diff(cx)
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Wait for the diff computation to complete
|
||||
if let Some(diff_task) = batch_diff_task {
|
||||
diff_task.await;
|
||||
}
|
||||
|
||||
finish_with_status(CodegenStatus::Done, cx);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(tool_use) if tool_use.name.as_ref() == "failure_message" => {
|
||||
// Handle failure message tool use
|
||||
match serde_json::from_value::<FailureMessageInput>(tool_use.input) {
|
||||
Ok(input) => {
|
||||
let _ = codegen.update(cx, |this, _cx| {
|
||||
// Store the failure message as the tool description
|
||||
this.model_explanation = Some(input.message.into());
|
||||
});
|
||||
finish_with_status(CodegenStatus::Done, cx);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(_tool_use) => {
|
||||
// Unexpected tool.
|
||||
finish_with_status(CodegenStatus::Done, cx);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
||||
@@ -387,9 +387,17 @@ impl InlineAssistant {
|
||||
let mut selections = Vec::<Selection<Point>>::new();
|
||||
let mut newest_selection = None;
|
||||
for mut selection in initial_selections {
|
||||
if selection.end == selection.start
|
||||
&& let Some(fold) =
|
||||
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||
if selection.end > selection.start {
|
||||
selection.start.column = 0;
|
||||
// If the selection ends at the start of the line, we don't want to include it.
|
||||
if selection.end.column == 0 {
|
||||
selection.end.row -= 1;
|
||||
}
|
||||
selection.end.column = snapshot
|
||||
.buffer_snapshot()
|
||||
.line_len(MultiBufferRow(selection.end.row));
|
||||
} else if let Some(fold) =
|
||||
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||
{
|
||||
selection.start = fold.range().start;
|
||||
selection.end = fold.range().end;
|
||||
@@ -416,15 +424,6 @@ impl InlineAssistant {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selection.start.column = 0;
|
||||
// If the selection ends at the start of the line, we don't want to include it.
|
||||
if selection.end.column == 0 && selection.start.row != selection.end.row {
|
||||
selection.end.row -= 1;
|
||||
}
|
||||
selection.end.column = snapshot
|
||||
.buffer_snapshot()
|
||||
.line_len(MultiBufferRow(selection.end.row));
|
||||
}
|
||||
|
||||
if let Some(prev_selection) = selections.last_mut()
|
||||
@@ -545,15 +544,14 @@ impl InlineAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
let [prompt_block_id, tool_description_block_id, end_block_id] =
|
||||
self.insert_assist_blocks(&editor, &range, &prompt_editor, cx);
|
||||
let [prompt_block_id, end_block_id] =
|
||||
self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
|
||||
|
||||
assists.push((
|
||||
assist_id,
|
||||
range.clone(),
|
||||
prompt_editor,
|
||||
prompt_block_id,
|
||||
tool_description_block_id,
|
||||
end_block_id,
|
||||
));
|
||||
}
|
||||
@@ -572,15 +570,7 @@ impl InlineAssistant {
|
||||
};
|
||||
|
||||
let mut assist_group = InlineAssistGroup::new();
|
||||
for (
|
||||
assist_id,
|
||||
range,
|
||||
prompt_editor,
|
||||
prompt_block_id,
|
||||
tool_description_block_id,
|
||||
end_block_id,
|
||||
) in assists
|
||||
{
|
||||
for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists {
|
||||
let codegen = prompt_editor.read(cx).codegen().clone();
|
||||
|
||||
self.assists.insert(
|
||||
@@ -591,7 +581,6 @@ impl InlineAssistant {
|
||||
editor,
|
||||
&prompt_editor,
|
||||
prompt_block_id,
|
||||
tool_description_block_id,
|
||||
end_block_id,
|
||||
range,
|
||||
codegen,
|
||||
@@ -700,7 +689,7 @@ impl InlineAssistant {
|
||||
range: &Range<Anchor>,
|
||||
prompt_editor: &Entity<PromptEditor<BufferCodegen>>,
|
||||
cx: &mut App,
|
||||
) -> [CustomBlockId; 3] {
|
||||
) -> [CustomBlockId; 2] {
|
||||
let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| {
|
||||
prompt_editor
|
||||
.editor
|
||||
@@ -714,14 +703,6 @@ impl InlineAssistant {
|
||||
render: build_assist_editor_renderer(prompt_editor),
|
||||
priority: 0,
|
||||
},
|
||||
// Placeholder for tool description - will be updated dynamically
|
||||
BlockProperties {
|
||||
style: BlockStyle::Flex,
|
||||
placement: BlockPlacement::Below(range.end),
|
||||
height: Some(0),
|
||||
render: Arc::new(|_cx| div().into_any_element()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Sticky,
|
||||
placement: BlockPlacement::Below(range.end),
|
||||
@@ -740,7 +721,7 @@ impl InlineAssistant {
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
let block_ids = editor.insert_blocks(assist_blocks, None, cx);
|
||||
[block_ids[0], block_ids[1], block_ids[2]]
|
||||
[block_ids[0], block_ids[1]]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1132,9 +1113,6 @@ impl InlineAssistant {
|
||||
let mut to_remove = decorations.removed_line_block_ids;
|
||||
to_remove.insert(decorations.prompt_block_id);
|
||||
to_remove.insert(decorations.end_block_id);
|
||||
if let Some(tool_description_block_id) = decorations.model_explanation {
|
||||
to_remove.insert(tool_description_block_id);
|
||||
}
|
||||
editor.remove_blocks(to_remove, None, cx);
|
||||
});
|
||||
|
||||
@@ -1455,60 +1433,8 @@ impl InlineAssistant {
|
||||
let old_snapshot = codegen.snapshot(cx);
|
||||
let old_buffer = codegen.old_buffer(cx);
|
||||
let deleted_row_ranges = codegen.diff(cx).deleted_row_ranges.clone();
|
||||
// let model_explanation = codegen.model_explanation(cx);
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
// Update tool description block
|
||||
// if let Some(description) = model_explanation {
|
||||
// if let Some(block_id) = decorations.model_explanation {
|
||||
// editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||
// let new_block_id = editor.insert_blocks(
|
||||
// [BlockProperties {
|
||||
// style: BlockStyle::Flex,
|
||||
// placement: BlockPlacement::Below(assist.range.end),
|
||||
// height: Some(1),
|
||||
// render: Arc::new({
|
||||
// let description = description.clone();
|
||||
// move |cx| {
|
||||
// div()
|
||||
// .w_full()
|
||||
// .py_1()
|
||||
// .px_2()
|
||||
// .bg(cx.theme().colors().editor_background)
|
||||
// .border_y_1()
|
||||
// .border_color(cx.theme().status().info_border)
|
||||
// .child(
|
||||
// Label::new(description.clone())
|
||||
// .color(Color::Muted)
|
||||
// .size(LabelSize::Small),
|
||||
// )
|
||||
// .into_any_element()
|
||||
// }
|
||||
// }),
|
||||
// priority: 0,
|
||||
// }],
|
||||
// None,
|
||||
// cx,
|
||||
// );
|
||||
// decorations.model_explanation = new_block_id.into_iter().next();
|
||||
// }
|
||||
// } else if let Some(block_id) = decorations.model_explanation {
|
||||
// // Hide the block if there's no description
|
||||
// editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||
// let new_block_id = editor.insert_blocks(
|
||||
// [BlockProperties {
|
||||
// style: BlockStyle::Flex,
|
||||
// placement: BlockPlacement::Below(assist.range.end),
|
||||
// height: Some(0),
|
||||
// render: Arc::new(|_cx| div().into_any_element()),
|
||||
// priority: 0,
|
||||
// }],
|
||||
// None,
|
||||
// cx,
|
||||
// );
|
||||
// decorations.model_explanation = new_block_id.into_iter().next();
|
||||
// }
|
||||
|
||||
let old_blocks = mem::take(&mut decorations.removed_line_block_ids);
|
||||
editor.remove_blocks(old_blocks, None, cx);
|
||||
|
||||
@@ -1760,7 +1686,6 @@ impl InlineAssist {
|
||||
editor: &Entity<Editor>,
|
||||
prompt_editor: &Entity<PromptEditor<BufferCodegen>>,
|
||||
prompt_block_id: CustomBlockId,
|
||||
tool_description_block_id: CustomBlockId,
|
||||
end_block_id: CustomBlockId,
|
||||
range: Range<Anchor>,
|
||||
codegen: Entity<BufferCodegen>,
|
||||
@@ -1775,8 +1700,7 @@ impl InlineAssist {
|
||||
decorations: Some(InlineAssistDecorations {
|
||||
prompt_block_id,
|
||||
prompt_editor: prompt_editor.clone(),
|
||||
removed_line_block_ids: Default::default(),
|
||||
model_explanation: Some(tool_description_block_id),
|
||||
removed_line_block_ids: HashSet::default(),
|
||||
end_block_id,
|
||||
}),
|
||||
range,
|
||||
@@ -1880,7 +1804,6 @@ struct InlineAssistDecorations {
|
||||
prompt_block_id: CustomBlockId,
|
||||
prompt_editor: Entity<PromptEditor<BufferCodegen>>,
|
||||
removed_line_block_ids: HashSet<CustomBlockId>,
|
||||
model_explanation: Option<CustomBlockId>,
|
||||
end_block_id: CustomBlockId,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,9 @@ use editor::{
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
AnyElement, App, Context, CursorStyle, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Subscription, TextStyle, TextStyleRefinement, WeakEntity, Window,
|
||||
Subscription, TextStyle, WeakEntity, Window,
|
||||
};
|
||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
|
||||
use parking_lot::Mutex;
|
||||
use project::Project;
|
||||
use prompt_store::PromptStore;
|
||||
@@ -66,7 +65,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
|
||||
const RIGHT_PADDING: Pixels = px(9.);
|
||||
|
||||
let (left_gutter_width, right_padding, explanation) = match &self.mode {
|
||||
let (left_gutter_width, right_padding) = match &self.mode {
|
||||
PromptEditorMode::Buffer {
|
||||
id: _,
|
||||
codegen,
|
||||
@@ -84,17 +83,11 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
let left_gutter_width = gutter.full_width() + (gutter.margin / 2.0);
|
||||
let right_padding = editor_margins.right + RIGHT_PADDING;
|
||||
|
||||
let explanation = codegen
|
||||
.active_alternative()
|
||||
.read(cx)
|
||||
.model_explanation
|
||||
.clone();
|
||||
|
||||
(left_gutter_width, right_padding, explanation)
|
||||
(left_gutter_width, right_padding)
|
||||
}
|
||||
PromptEditorMode::Terminal { .. } => {
|
||||
// Give the equivalent of the same left-padding that we're using on the right
|
||||
(Pixels::from(40.0), Pixels::from(24.), None)
|
||||
(Pixels::from(40.0), Pixels::from(24.))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,30 +111,18 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
this.trigger_completion_menu(window, cx);
|
||||
}));
|
||||
|
||||
let markdown = window.use_state(cx, |_, cx| Markdown::new("".into(), None, None, cx));
|
||||
|
||||
if let Some(explanation) = &explanation {
|
||||
markdown.update(cx, |markdown, cx| {
|
||||
markdown.reset(explanation.clone(), cx);
|
||||
});
|
||||
}
|
||||
|
||||
let explanation_label = self
|
||||
.render_markdown(markdown, markdown_style(window, cx))
|
||||
.into_any_element();
|
||||
|
||||
v_flex()
|
||||
.key_context("PromptEditor")
|
||||
.capture_action(cx.listener(Self::paste))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.block_mouse_except_scroll()
|
||||
.gap_0p5()
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.size_full()
|
||||
.pt_0p5()
|
||||
.pb(bottom_padding)
|
||||
.pr(right_padding)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.gap_0p5()
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
@@ -158,12 +139,12 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
.capture_action(cx.listener(Self::cycle_next))
|
||||
.child(
|
||||
WithRemSize::new(ui_font_size)
|
||||
.h_full()
|
||||
.w(left_gutter_width)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.flex_shrink_0()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.w(left_gutter_width)
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(self.render_close_button(cx))
|
||||
@@ -196,82 +177,26 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(add_context_button)
|
||||
.child(self.model_selector.clone())
|
||||
.children(buttons),
|
||||
),
|
||||
),
|
||||
)
|
||||
.when_some(explanation, |this, _| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.child(div().w(left_gutter_width + px(6.)))
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.min_w_0()
|
||||
.pb_px()
|
||||
.pl_1()
|
||||
.flex_1()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(explanation_label),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
let colors = cx.theme().colors();
|
||||
let mut text_style = window.text_style();
|
||||
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
color: Some(colors.text),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: colors.element_selection_background,
|
||||
heading_level_styles: Some(HeadingLevelStyles {
|
||||
h1: Some(TextStyleRefinement {
|
||||
font_size: Some(rems(1.15).into()),
|
||||
..Default::default()
|
||||
}),
|
||||
h2: Some(TextStyleRefinement {
|
||||
font_size: Some(rems(1.1).into()),
|
||||
..Default::default()
|
||||
}),
|
||||
h3: Some(TextStyleRefinement {
|
||||
font_size: Some(rems(1.05).into()),
|
||||
..Default::default()
|
||||
}),
|
||||
h4: Some(TextStyleRefinement {
|
||||
font_size: Some(rems(1.).into()),
|
||||
..Default::default()
|
||||
}),
|
||||
h5: Some(TextStyleRefinement {
|
||||
font_size: Some(rems(0.95).into()),
|
||||
..Default::default()
|
||||
}),
|
||||
h6: Some(TextStyleRefinement {
|
||||
font_size: Some(rems(0.875).into()),
|
||||
..Default::default()
|
||||
}),
|
||||
}),
|
||||
inline_code: TextStyleRefinement {
|
||||
font_family: Some(theme_settings.buffer_font.family.clone()),
|
||||
font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
|
||||
font_features: Some(theme_settings.buffer_font.features.clone()),
|
||||
background_color: Some(colors.editor_foreground.opacity(0.08)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
.child(
|
||||
WithRemSize::new(ui_font_size)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.child(h_flex().flex_shrink_0().w(left_gutter_width))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.pl_1()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.child(add_context_button)
|
||||
.child(self.model_selector.clone()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,10 +759,6 @@ impl<T: 'static> PromptEditor<T> {
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_markdown(&self, markdown: Entity<Markdown>, style: MarkdownStyle) -> MarkdownElement {
|
||||
MarkdownElement::new(markdown, style)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PromptEditorMode {
|
||||
|
||||
@@ -2622,11 +2622,13 @@ impl SearchableItem for TextThreadEditor {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
matches: &[Self::Match],
|
||||
active_match_index: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.update_matches(matches, window, cx));
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.update_matches(matches, active_match_index, window, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
||||
|
||||
@@ -1017,11 +1017,13 @@ impl SearchableItem for DapLogView {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
matches: &[Self::Match],
|
||||
active_match_index: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor
|
||||
.update(cx, |e, cx| e.update_matches(matches, window, cx))
|
||||
self.editor.update(cx, |e, cx| {
|
||||
e.update_matches(matches, active_match_index, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
||||
|
||||
@@ -252,10 +252,11 @@ impl Console {
|
||||
let start_offset = range.start;
|
||||
let range = buffer.anchor_after(MultiBufferOffset(range.start))
|
||||
..buffer.anchor_before(MultiBufferOffset(range.end));
|
||||
let color_fn = color_fetcher(color);
|
||||
console.highlight_background_key::<ConsoleAnsiHighlight>(
|
||||
start_offset,
|
||||
&[range],
|
||||
color_fetcher(color),
|
||||
move |_, theme| color_fn(theme),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -726,7 +726,10 @@ impl EditorActionId {
|
||||
// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
|
||||
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
|
||||
|
||||
type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
|
||||
type BackgroundHighlight = (
|
||||
Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
|
||||
Arc<[Range<Anchor>]>,
|
||||
);
|
||||
type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -6610,7 +6613,7 @@ impl Editor {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.highlight_background::<Self>(
|
||||
&ranges_to_highlight,
|
||||
|theme| theme.colors().editor_highlighted_line_background,
|
||||
|_, theme| theme.colors().editor_highlighted_line_background,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -7012,12 +7015,12 @@ impl Editor {
|
||||
|
||||
this.highlight_background::<DocumentHighlightRead>(
|
||||
&read_ranges,
|
||||
|theme| theme.colors().editor_document_highlight_read_background,
|
||||
|_, theme| theme.colors().editor_document_highlight_read_background,
|
||||
cx,
|
||||
);
|
||||
this.highlight_background::<DocumentHighlightWrite>(
|
||||
&write_ranges,
|
||||
|theme| theme.colors().editor_document_highlight_write_background,
|
||||
|_, theme| theme.colors().editor_document_highlight_write_background,
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
@@ -7125,7 +7128,7 @@ impl Editor {
|
||||
if !match_ranges.is_empty() {
|
||||
editor.highlight_background::<SelectedTextHighlight>(
|
||||
&match_ranges,
|
||||
|theme| theme.colors().editor_document_highlight_bracket_background,
|
||||
|_, theme| theme.colors().editor_document_highlight_bracket_background,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -17012,7 +17015,9 @@ impl Editor {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let workspace = self.workspace();
|
||||
let Some(workspace) = self.workspace() else {
|
||||
return Task::ready(Ok(Navigated::No));
|
||||
};
|
||||
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
let locations: Vec<Location> = future::join_all(definitions)
|
||||
@@ -17038,10 +17043,6 @@ impl Editor {
|
||||
}
|
||||
|
||||
if num_locations > 1 {
|
||||
let Some(workspace) = workspace else {
|
||||
return Ok(Navigated::No);
|
||||
};
|
||||
|
||||
let tab_kind = match kind {
|
||||
Some(GotoDefinitionKind::Implementation) => "Implementations",
|
||||
Some(GotoDefinitionKind::Symbol) | None => "Definitions",
|
||||
@@ -17073,11 +17074,14 @@ impl Editor {
|
||||
|
||||
let opened = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
let allow_preview = PreviewTabsSettings::get_global(cx)
|
||||
.enable_preview_multibuffer_from_code_navigation;
|
||||
Self::open_locations_in_multibuffer(
|
||||
workspace,
|
||||
locations,
|
||||
title,
|
||||
split,
|
||||
allow_preview,
|
||||
MultibufferSelectionMode::First,
|
||||
window,
|
||||
cx,
|
||||
@@ -17094,10 +17098,9 @@ impl Editor {
|
||||
Ok(Navigated::Yes)
|
||||
}
|
||||
Some(Either::Right(path)) => {
|
||||
let Some(workspace) = workspace else {
|
||||
return Ok(Navigated::No);
|
||||
};
|
||||
|
||||
// TODO(andrew): respect preview tab settings
|
||||
// `enable_keep_preview_on_code_navigation` and
|
||||
// `enable_preview_file_from_code_navigation`
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_resolved_path(path, window, cx)
|
||||
@@ -17108,10 +17111,6 @@ impl Editor {
|
||||
None => Ok(Navigated::No),
|
||||
}
|
||||
} else {
|
||||
let Some(workspace) = workspace else {
|
||||
return Ok(Navigated::No);
|
||||
};
|
||||
|
||||
let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
|
||||
let target_range = target_ranges.first().unwrap().clone();
|
||||
|
||||
@@ -17135,11 +17134,19 @@ impl Editor {
|
||||
workspace.active_pane().clone()
|
||||
};
|
||||
|
||||
let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
|
||||
let keep_old_preview = preview_tabs_settings
|
||||
.enable_keep_preview_on_code_navigation;
|
||||
let allow_new_preview = preview_tabs_settings
|
||||
.enable_preview_file_from_code_navigation;
|
||||
|
||||
workspace.open_project_item(
|
||||
pane,
|
||||
target_buffer.clone(),
|
||||
true,
|
||||
true,
|
||||
keep_old_preview,
|
||||
allow_new_preview,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -17416,11 +17423,14 @@ impl Editor {
|
||||
} else {
|
||||
format!("References to {target}")
|
||||
};
|
||||
let allow_preview = PreviewTabsSettings::get_global(cx)
|
||||
.enable_preview_multibuffer_from_code_navigation;
|
||||
Self::open_locations_in_multibuffer(
|
||||
workspace,
|
||||
locations,
|
||||
title,
|
||||
false,
|
||||
allow_preview,
|
||||
MultibufferSelectionMode::First,
|
||||
window,
|
||||
cx,
|
||||
@@ -17436,6 +17446,7 @@ impl Editor {
|
||||
locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
|
||||
title: String,
|
||||
split: bool,
|
||||
allow_preview: bool,
|
||||
multibuffer_selection_mode: MultibufferSelectionMode,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
@@ -17483,6 +17494,7 @@ impl Editor {
|
||||
.is_some_and(|it| *it == key)
|
||||
})
|
||||
});
|
||||
let was_existing = existing.is_some();
|
||||
let editor = existing.unwrap_or_else(|| {
|
||||
cx.new(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(
|
||||
@@ -17510,7 +17522,7 @@ impl Editor {
|
||||
}
|
||||
editor.highlight_background::<Self>(
|
||||
&ranges,
|
||||
|theme| theme.colors().editor_highlighted_line_background,
|
||||
|_, theme| theme.colors().editor_highlighted_line_background,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -17523,29 +17535,23 @@ impl Editor {
|
||||
});
|
||||
|
||||
let item = Box::new(editor);
|
||||
let item_id = item.item_id();
|
||||
|
||||
if split {
|
||||
let pane = workspace.adjacent_pane(window, cx);
|
||||
workspace.add_item(pane, item, None, true, true, window, cx);
|
||||
} else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
|
||||
let (preview_item_id, preview_item_idx) =
|
||||
workspace.active_pane().read_with(cx, |pane, _| {
|
||||
(pane.preview_item_id(), pane.preview_item_idx())
|
||||
});
|
||||
|
||||
workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
|
||||
|
||||
if let Some(preview_item_id) = preview_item_id {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.remove_item(preview_item_id, false, false, window, cx);
|
||||
});
|
||||
}
|
||||
let pane = if split {
|
||||
workspace.adjacent_pane(window, cx)
|
||||
} else {
|
||||
workspace.add_item_to_active_pane(item, None, true, window, cx);
|
||||
}
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.set_preview_item_id(Some(item_id), cx);
|
||||
workspace.active_pane().clone()
|
||||
};
|
||||
let activate_pane = split;
|
||||
|
||||
let mut destination_index = None;
|
||||
pane.update(cx, |pane, cx| {
|
||||
if allow_preview && !was_existing {
|
||||
destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
|
||||
}
|
||||
if was_existing && !allow_preview {
|
||||
pane.unpreview_item_if_preview(item.item_id());
|
||||
}
|
||||
pane.add_item(item, activate_pane, true, destination_index, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20783,6 +20789,7 @@ impl Editor {
|
||||
locations,
|
||||
format!("Selections for '{title}'"),
|
||||
false,
|
||||
false,
|
||||
MultibufferSelectionMode::All,
|
||||
window,
|
||||
cx,
|
||||
@@ -20985,7 +20992,7 @@ impl Editor {
|
||||
pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
|
||||
self.highlight_background::<SearchWithinRange>(
|
||||
ranges,
|
||||
|colors| colors.colors().editor_document_highlight_read_background,
|
||||
|_, colors| colors.colors().editor_document_highlight_read_background,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -21001,12 +21008,12 @@ impl Editor {
|
||||
pub fn highlight_background<T: 'static>(
|
||||
&mut self,
|
||||
ranges: &[Range<Anchor>],
|
||||
color_fetcher: fn(&Theme) -> Hsla,
|
||||
color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.background_highlights.insert(
|
||||
HighlightKey::Type(TypeId::of::<T>()),
|
||||
(color_fetcher, Arc::from(ranges)),
|
||||
(Arc::new(color_fetcher), Arc::from(ranges)),
|
||||
);
|
||||
self.scrollbar_marker_state.dirty = true;
|
||||
cx.notify();
|
||||
@@ -21016,12 +21023,12 @@ impl Editor {
|
||||
&mut self,
|
||||
key: usize,
|
||||
ranges: &[Range<Anchor>],
|
||||
color_fetcher: fn(&Theme) -> Hsla,
|
||||
color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.background_highlights.insert(
|
||||
HighlightKey::TypePlus(TypeId::of::<T>(), key),
|
||||
(color_fetcher, Arc::from(ranges)),
|
||||
(Arc::new(color_fetcher), Arc::from(ranges)),
|
||||
);
|
||||
self.scrollbar_marker_state.dirty = true;
|
||||
cx.notify();
|
||||
@@ -21246,7 +21253,6 @@ impl Editor {
|
||||
) -> Vec<(Range<DisplayPoint>, Hsla)> {
|
||||
let mut results = Vec::new();
|
||||
for (color_fetcher, ranges) in self.background_highlights.values() {
|
||||
let color = color_fetcher(theme);
|
||||
let start_ix = match ranges.binary_search_by(|probe| {
|
||||
let cmp = probe
|
||||
.end
|
||||
@@ -21259,7 +21265,7 @@ impl Editor {
|
||||
}) {
|
||||
Ok(i) | Err(i) => i,
|
||||
};
|
||||
for range in &ranges[start_ix..] {
|
||||
for (index, range) in ranges[start_ix..].iter().enumerate() {
|
||||
if range
|
||||
.start
|
||||
.cmp(&search_range.end, &display_snapshot.buffer_snapshot())
|
||||
@@ -21268,6 +21274,7 @@ impl Editor {
|
||||
break;
|
||||
}
|
||||
|
||||
let color = color_fetcher(&(start_ix + index), theme);
|
||||
let start = range.start.to_display_point(display_snapshot);
|
||||
let end = range.end.to_display_point(display_snapshot);
|
||||
results.push((start..end, color))
|
||||
@@ -22002,29 +22009,40 @@ impl Editor {
|
||||
// Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
|
||||
// so `workspace.open_project_item` will never find them, always opening a new editor.
|
||||
// Instead, we try to activate the existing editor in the pane first.
|
||||
let (editor, pane_item_index) =
|
||||
let (editor, pane_item_index, pane_item_id) =
|
||||
pane.read(cx).items().enumerate().find_map(|(i, item)| {
|
||||
let editor = item.downcast::<Editor>()?;
|
||||
let singleton_buffer =
|
||||
editor.read(cx).buffer().read(cx).as_singleton()?;
|
||||
if singleton_buffer == buffer {
|
||||
Some((editor, i))
|
||||
Some((editor, i, item.item_id()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(pane_item_index, true, true, window, cx)
|
||||
pane.activate_item(pane_item_index, true, true, window, cx);
|
||||
if !PreviewTabsSettings::get_global(cx)
|
||||
.enable_preview_from_multibuffer
|
||||
{
|
||||
pane.unpreview_item_if_preview(pane_item_id);
|
||||
}
|
||||
});
|
||||
Some(editor)
|
||||
})
|
||||
.flatten()
|
||||
.unwrap_or_else(|| {
|
||||
let keep_old_preview = PreviewTabsSettings::get_global(cx)
|
||||
.enable_keep_preview_on_code_navigation;
|
||||
let allow_new_preview =
|
||||
PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
|
||||
workspace.open_project_item::<Self>(
|
||||
pane.clone(),
|
||||
buffer,
|
||||
true,
|
||||
true,
|
||||
keep_old_preview,
|
||||
allow_new_preview,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -16978,7 +16978,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
|
||||
anchor_range(Point::new(6, 3)..Point::new(6, 5)),
|
||||
anchor_range(Point::new(8, 4)..Point::new(8, 6)),
|
||||
],
|
||||
|_| Hsla::red(),
|
||||
|_, _| Hsla::red(),
|
||||
cx,
|
||||
);
|
||||
editor.highlight_background::<Type2>(
|
||||
@@ -16988,7 +16988,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
|
||||
anchor_range(Point::new(7, 4)..Point::new(7, 7)),
|
||||
anchor_range(Point::new(9, 5)..Point::new(9, 8)),
|
||||
],
|
||||
|_| Hsla::green(),
|
||||
|_, _| Hsla::green(),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -23973,7 +23973,7 @@ async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
|
||||
let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
|
||||
editor.highlight_background::<DocumentHighlightRead>(
|
||||
&[highlight_range],
|
||||
|theme| theme.colors().editor_document_highlight_read_background,
|
||||
|_, theme| theme.colors().editor_document_highlight_read_background,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -24051,7 +24051,7 @@ async fn test_rename_without_prepare(cx: &mut TestAppContext) {
|
||||
let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
|
||||
editor.highlight_background::<DocumentHighlightRead>(
|
||||
&[highlight_range],
|
||||
|theme| theme.colors().editor_document_highlight_read_background,
|
||||
|_, theme| theme.colors().editor_document_highlight_read_background,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -27299,7 +27299,7 @@ let result = variable * 2;",
|
||||
|
||||
editor.highlight_background::<DocumentHighlightRead>(
|
||||
&anchor_ranges,
|
||||
|theme| theme.colors().editor_document_highlight_read_background,
|
||||
|_, theme| theme.colors().editor_document_highlight_read_background,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -518,7 +518,7 @@ fn show_hover(
|
||||
// Highlight the selected symbol using a background highlight
|
||||
editor.highlight_background::<HoverState>(
|
||||
&hover_highlights,
|
||||
|theme| theme.colors().element_hover, // todo update theme
|
||||
|_, theme| theme.colors().element_hover, // todo update theme
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1487,6 +1487,7 @@ impl SearchableItem for Editor {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
matches: &[Range<Anchor>],
|
||||
active_match_index: Option<usize>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -1497,7 +1498,13 @@ impl SearchableItem for Editor {
|
||||
let updated = existing_range != Some(matches);
|
||||
self.highlight_background::<BufferSearchHighlights>(
|
||||
matches,
|
||||
|theme| theme.colors().search_match_background,
|
||||
move |index, theme| {
|
||||
if active_match_index == Some(*index) {
|
||||
theme.colors().search_active_match_background
|
||||
} else {
|
||||
theme.colors().search_match_background
|
||||
}
|
||||
},
|
||||
cx,
|
||||
);
|
||||
if updated {
|
||||
|
||||
@@ -17,9 +17,3 @@ pub struct PanicFeatureFlag;
|
||||
impl FeatureFlag for PanicFeatureFlag {
|
||||
const NAME: &'static str = "panic";
|
||||
}
|
||||
|
||||
pub struct InlineAssistantV2FeatureFlag;
|
||||
|
||||
impl FeatureFlag for InlineAssistantV2FeatureFlag {
|
||||
const NAME: &'static str = "inline-assistant-v2";
|
||||
}
|
||||
|
||||
@@ -4004,28 +4004,21 @@ impl GitPanel {
|
||||
"Restore File"
|
||||
};
|
||||
let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||
let mut context_menu = context_menu
|
||||
let is_created = entry.status.is_created();
|
||||
context_menu
|
||||
.context(self.focus_handle.clone())
|
||||
.action(stage_title, ToggleStaged.boxed_clone())
|
||||
.action(restore_title, git::RestoreFile::default().boxed_clone());
|
||||
|
||||
if entry.status.is_created() {
|
||||
context_menu =
|
||||
context_menu.action("Add to .gitignore", git::AddToGitignore.boxed_clone())
|
||||
}
|
||||
|
||||
context_menu = context_menu
|
||||
.action(restore_title, git::RestoreFile::default().boxed_clone())
|
||||
.action_disabled_when(
|
||||
!is_created,
|
||||
"Add to .gitignore",
|
||||
git::AddToGitignore.boxed_clone(),
|
||||
)
|
||||
.separator()
|
||||
.action("Open Diff", Confirm.boxed_clone())
|
||||
.action("Open File", SecondaryConfirm.boxed_clone());
|
||||
|
||||
if !entry.status.is_created() {
|
||||
context_menu = context_menu
|
||||
.separator()
|
||||
.action("File History", Box::new(git::FileHistory));
|
||||
}
|
||||
|
||||
context_menu
|
||||
.action("Open File", SecondaryConfirm.boxed_clone())
|
||||
.separator()
|
||||
.action_disabled_when(is_created, "File History", Box::new(git::FileHistory))
|
||||
});
|
||||
self.selected_entry = Some(ix);
|
||||
self.set_context_menu(context_menu, position, window, cx);
|
||||
|
||||
@@ -707,40 +707,6 @@ pub trait LanguageModel: Send + Sync {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn stream_completion_tool(
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<LanguageModelToolUse, LanguageModelCompletionError>> {
|
||||
let future = self.stream_completion(request, cx);
|
||||
|
||||
async move {
|
||||
let events = future.await?;
|
||||
let mut events = events.fuse();
|
||||
|
||||
// Iterate through events until we find a complete ToolUse
|
||||
while let Some(event) = events.next().await {
|
||||
match event {
|
||||
Ok(LanguageModelCompletionEvent::ToolUse(tool_use))
|
||||
if tool_use.is_input_complete =>
|
||||
{
|
||||
return Ok(tool_use);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Stream ended without a complete tool use
|
||||
Err(LanguageModelCompletionError::Other(anyhow::anyhow!(
|
||||
"Stream ended without receiving a complete tool use"
|
||||
)))
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn cache_configuration(&self) -> Option<LanguageModelCacheConfiguration> {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -805,11 +805,13 @@ impl SearchableItem for LspLogView {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
matches: &[Self::Match],
|
||||
active_match_index: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor
|
||||
.update(cx, |e, cx| e.update_matches(matches, window, cx))
|
||||
self.editor.update(cx, |e, cx| {
|
||||
e.update_matches(matches, active_match_index, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
||||
|
||||
@@ -459,7 +459,7 @@ impl SyntaxTreeView {
|
||||
editor.clear_background_highlights::<Self>(cx);
|
||||
editor.highlight_background::<Self>(
|
||||
&[range],
|
||||
|theme| {
|
||||
|_, theme| {
|
||||
theme
|
||||
.colors()
|
||||
.editor_document_highlight_write_background
|
||||
|
||||
@@ -153,3 +153,9 @@ pub(crate) mod m_2025_11_25 {
|
||||
|
||||
pub(crate) use settings::remove_context_server_source;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_12_01 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
55
crates/migrator/src/migrations/m_2025_12_01/settings.rs
Normal file
55
crates/migrator/src/migrations/m_2025_12_01/settings.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
|
||||
SETTINGS_NESTED_KEY_VALUE_PATTERN,
|
||||
rename_enable_preview_from_code_navigation_setting,
|
||||
)];
|
||||
|
||||
fn rename_enable_preview_from_code_navigation_setting(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
if !is_enable_preview_from_code_navigation(contents, mat, query) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let setting_name_ix = query.capture_index_for_name("setting_name")?;
|
||||
let setting_name_range = mat
|
||||
.nodes_for_capture_index(setting_name_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
|
||||
Some((
|
||||
setting_name_range,
|
||||
"enable_keep_preview_on_code_navigation".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
fn is_enable_preview_from_code_navigation(contents: &str, mat: &QueryMatch, query: &Query) -> bool {
|
||||
let parent_key_ix = match query.capture_index_for_name("parent_key") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let parent_range = match mat.nodes_for_capture_index(parent_key_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
if contents.get(parent_range) != Some("preview_tabs") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let setting_name_ix = match query.capture_index_for_name("setting_name") {
|
||||
Some(ix) => ix,
|
||||
None => return false,
|
||||
};
|
||||
let setting_name_range = match mat.nodes_for_capture_index(setting_name_ix).next() {
|
||||
Some(node) => node.byte_range(),
|
||||
None => return false,
|
||||
};
|
||||
contents.get(setting_name_range) == Some("enable_preview_from_code_navigation")
|
||||
}
|
||||
@@ -219,6 +219,10 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
||||
migrations::m_2025_11_12::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_11_12,
|
||||
),
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_12_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_12_01,
|
||||
),
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_11_20::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_11_20,
|
||||
@@ -346,6 +350,10 @@ define_query!(
|
||||
SETTINGS_QUERY_2025_11_12,
|
||||
migrations::m_2025_11_12::SETTINGS_PATTERNS
|
||||
);
|
||||
define_query!(
|
||||
SETTINGS_QUERY_2025_12_01,
|
||||
migrations::m_2025_12_01::SETTINGS_PATTERNS
|
||||
);
|
||||
define_query!(
|
||||
SETTINGS_QUERY_2025_11_20,
|
||||
migrations::m_2025_11_20::SETTINGS_PATTERNS
|
||||
@@ -2262,51 +2270,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_panel_open_file_on_paste_migration() {
|
||||
assert_migrate_settings(
|
||||
&r#"
|
||||
{
|
||||
"project_panel": {
|
||||
"open_file_on_paste": true
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"project_panel": {
|
||||
"auto_open": { "on_paste": true }
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
|
||||
assert_migrate_settings(
|
||||
&r#"
|
||||
{
|
||||
"project_panel": {
|
||||
"open_file_on_paste": false
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"project_panel": {
|
||||
"auto_open": { "on_paste": false }
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_context_server_source() {
|
||||
assert_migrate_settings(
|
||||
@@ -2354,4 +2317,102 @@ mod tests {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_panel_open_file_on_paste_migration() {
|
||||
assert_migrate_settings(
|
||||
&r#"
|
||||
{
|
||||
"project_panel": {
|
||||
"open_file_on_paste": true
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"project_panel": {
|
||||
"auto_open": { "on_paste": true }
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
|
||||
assert_migrate_settings(
|
||||
&r#"
|
||||
{
|
||||
"project_panel": {
|
||||
"open_file_on_paste": false
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"project_panel": {
|
||||
"auto_open": { "on_paste": false }
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enable_preview_from_code_navigation_migration() {
|
||||
assert_migrate_settings(
|
||||
&r#"
|
||||
{
|
||||
"other_setting_1": 1,
|
||||
"preview_tabs": {
|
||||
"other_setting_2": 2,
|
||||
"enable_preview_from_code_navigation": false
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"other_setting_1": 1,
|
||||
"preview_tabs": {
|
||||
"other_setting_2": 2,
|
||||
"enable_keep_preview_on_code_navigation": false
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
|
||||
assert_migrate_settings(
|
||||
&r#"
|
||||
{
|
||||
"other_setting_1": 1,
|
||||
"preview_tabs": {
|
||||
"other_setting_2": 2,
|
||||
"enable_preview_from_code_navigation": true
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"other_setting_1": 1,
|
||||
"preview_tabs": {
|
||||
"other_setting_2": 2,
|
||||
"enable_keep_preview_on_code_navigation": true
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1529,7 +1529,8 @@ impl ProjectPanel {
|
||||
}
|
||||
|
||||
fn open(&mut self, _: &Open, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let preview_tabs_enabled = PreviewTabsSettings::get_global(cx).enabled;
|
||||
let preview_tabs_enabled =
|
||||
PreviewTabsSettings::get_global(cx).enable_preview_from_project_panel;
|
||||
self.open_internal(true, !preview_tabs_enabled, None, window, cx);
|
||||
}
|
||||
|
||||
@@ -4819,7 +4820,7 @@ impl ProjectPanel {
|
||||
project_panel.toggle_expanded(entry_id, window, cx);
|
||||
}
|
||||
} else {
|
||||
let preview_tabs_enabled = PreviewTabsSettings::get_global(cx).enabled;
|
||||
let preview_tabs_enabled = PreviewTabsSettings::get_global(cx).enable_preview_from_project_panel;
|
||||
let click_count = event.click_count();
|
||||
let focus_opened_item = click_count > 1;
|
||||
let allow_preview = preview_tabs_enabled && click_count == 1;
|
||||
|
||||
@@ -133,8 +133,9 @@ impl PickerDelegate for ProjectSymbolsDelegate {
|
||||
workspace.active_pane().clone()
|
||||
};
|
||||
|
||||
let editor =
|
||||
workspace.open_project_item::<Editor>(pane, buffer, true, true, window, cx);
|
||||
let editor = workspace.open_project_item::<Editor>(
|
||||
pane, buffer, true, true, true, true, window, cx,
|
||||
);
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(
|
||||
|
||||
@@ -94,16 +94,6 @@ pub struct ContentPromptContext {
|
||||
pub diagnostic_errors: Vec<ContentPromptDiagnosticContext>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ContentPromptContextV2 {
|
||||
pub content_type: String,
|
||||
pub language_name: Option<String>,
|
||||
pub is_truncated: bool,
|
||||
pub document_content: String,
|
||||
pub rewrite_section: Option<String>,
|
||||
pub diagnostic_errors: Vec<ContentPromptDiagnosticContext>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TerminalAssistantPromptContext {
|
||||
pub os: String,
|
||||
@@ -286,88 +276,6 @@ impl PromptBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_inline_transformation_prompt_v2(
|
||||
&self,
|
||||
language_name: Option<&LanguageName>,
|
||||
buffer: BufferSnapshot,
|
||||
range: Range<usize>,
|
||||
) -> Result<String, RenderError> {
|
||||
let content_type = match language_name.as_ref().map(|l| l.as_ref()) {
|
||||
None | Some("Markdown" | "Plain Text") => "text",
|
||||
Some(_) => "code",
|
||||
};
|
||||
|
||||
const MAX_CTX: usize = 50000;
|
||||
let is_insert = range.is_empty();
|
||||
let mut is_truncated = false;
|
||||
|
||||
let before_range = 0..range.start;
|
||||
let truncated_before = if before_range.len() > MAX_CTX {
|
||||
is_truncated = true;
|
||||
let start = buffer.clip_offset(range.start - MAX_CTX, text::Bias::Right);
|
||||
start..range.start
|
||||
} else {
|
||||
before_range
|
||||
};
|
||||
|
||||
let after_range = range.end..buffer.len();
|
||||
let truncated_after = if after_range.len() > MAX_CTX {
|
||||
is_truncated = true;
|
||||
let end = buffer.clip_offset(range.end + MAX_CTX, text::Bias::Left);
|
||||
range.end..end
|
||||
} else {
|
||||
after_range
|
||||
};
|
||||
|
||||
let mut document_content = String::new();
|
||||
for chunk in buffer.text_for_range(truncated_before) {
|
||||
document_content.push_str(chunk);
|
||||
}
|
||||
if is_insert {
|
||||
document_content.push_str("<insert_here></insert_here>");
|
||||
} else {
|
||||
document_content.push_str("<rewrite_this>\n");
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
document_content.push_str(chunk);
|
||||
}
|
||||
document_content.push_str("\n</rewrite_this>");
|
||||
}
|
||||
for chunk in buffer.text_for_range(truncated_after) {
|
||||
document_content.push_str(chunk);
|
||||
}
|
||||
|
||||
let rewrite_section = if !is_insert {
|
||||
let mut section = String::new();
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
section.push_str(chunk);
|
||||
}
|
||||
Some(section)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let diagnostics = buffer.diagnostics_in_range::<_, Point>(range, false);
|
||||
let diagnostic_errors: Vec<ContentPromptDiagnosticContext> = diagnostics
|
||||
.map(|entry| {
|
||||
let start = entry.range.start;
|
||||
ContentPromptDiagnosticContext {
|
||||
line_number: (start.row + 1) as usize,
|
||||
error_message: entry.diagnostic.message.clone(),
|
||||
code_content: buffer.text_for_range(entry.range).collect(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let context = ContentPromptContextV2 {
|
||||
content_type: content_type.to_string(),
|
||||
language_name: language_name.map(|s| s.to_string()),
|
||||
is_truncated,
|
||||
document_content,
|
||||
rewrite_section,
|
||||
diagnostic_errors,
|
||||
};
|
||||
self.handlebars.lock().render("content_prompt_v2", &context)
|
||||
}
|
||||
|
||||
pub fn generate_inline_transformation_prompt(
|
||||
&self,
|
||||
user_prompt: String,
|
||||
|
||||
@@ -1031,7 +1031,7 @@ impl BufferSearchBar {
|
||||
let new_match_index = searchable_item
|
||||
.match_index_for_direction(matches, index, direction, count, window, cx);
|
||||
|
||||
searchable_item.update_matches(matches, window, cx);
|
||||
searchable_item.update_matches(matches, Some(new_match_index), window, cx);
|
||||
searchable_item.activate_match(new_match_index, matches, window, cx);
|
||||
}
|
||||
}
|
||||
@@ -1045,7 +1045,7 @@ impl BufferSearchBar {
|
||||
if matches.is_empty() {
|
||||
return;
|
||||
}
|
||||
searchable_item.update_matches(matches, window, cx);
|
||||
searchable_item.update_matches(matches, Some(0), window, cx);
|
||||
searchable_item.activate_match(0, matches, window, cx);
|
||||
}
|
||||
}
|
||||
@@ -1060,7 +1060,7 @@ impl BufferSearchBar {
|
||||
return;
|
||||
}
|
||||
let new_match_index = matches.len() - 1;
|
||||
searchable_item.update_matches(matches, window, cx);
|
||||
searchable_item.update_matches(matches, Some(new_match_index), window, cx);
|
||||
searchable_item.activate_match(new_match_index, matches, window, cx);
|
||||
}
|
||||
}
|
||||
@@ -1300,7 +1300,12 @@ impl BufferSearchBar {
|
||||
if matches.is_empty() {
|
||||
active_searchable_item.clear_matches(window, cx);
|
||||
} else {
|
||||
active_searchable_item.update_matches(matches, window, cx);
|
||||
active_searchable_item.update_matches(
|
||||
matches,
|
||||
this.active_match_index,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
let _ = done_tx.send(());
|
||||
}
|
||||
@@ -1335,6 +1340,18 @@ impl BufferSearchBar {
|
||||
});
|
||||
if new_index != self.active_match_index {
|
||||
self.active_match_index = new_index;
|
||||
if !self.dismissed {
|
||||
if let Some(searchable_item) = self.active_searchable_item.as_ref() {
|
||||
if let Some(matches) = self
|
||||
.searchable_items_with_matches
|
||||
.get(&searchable_item.downgrade())
|
||||
{
|
||||
if !matches.is_empty() {
|
||||
searchable_item.update_matches(matches, new_index, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1444,6 +1444,7 @@ impl ProjectSearchView {
|
||||
s.select_ranges([range_to_select])
|
||||
});
|
||||
});
|
||||
self.highlight_matches(&match_ranges, Some(new_index), cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1518,11 +1519,6 @@ impl ProjectSearchView {
|
||||
});
|
||||
editor.scroll(Point::default(), Some(Axis::Vertical), window, cx);
|
||||
}
|
||||
editor.highlight_background::<Self>(
|
||||
&match_ranges,
|
||||
|theme| theme.colors().search_match_background,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
if is_new_search && self.query_editor.focus_handle(cx).is_focused(window) {
|
||||
self.focus_results_editor(window, cx);
|
||||
@@ -1535,18 +1531,41 @@ impl ProjectSearchView {
|
||||
|
||||
fn update_match_index(&mut self, cx: &mut Context<Self>) {
|
||||
let results_editor = self.results_editor.read(cx);
|
||||
let match_ranges = self.entity.read(cx).match_ranges.clone();
|
||||
let new_index = active_match_index(
|
||||
Direction::Next,
|
||||
&self.entity.read(cx).match_ranges,
|
||||
&match_ranges,
|
||||
&results_editor.selections.newest_anchor().head(),
|
||||
&results_editor.buffer().read(cx).snapshot(cx),
|
||||
);
|
||||
self.highlight_matches(&match_ranges, new_index, cx);
|
||||
if self.active_match_index != new_index {
|
||||
self.active_match_index = new_index;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_matches(
|
||||
&self,
|
||||
match_ranges: &[Range<Anchor>],
|
||||
active_index: Option<usize>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.results_editor.update(cx, |editor, cx| {
|
||||
editor.highlight_background::<Self>(
|
||||
match_ranges,
|
||||
move |index, theme| {
|
||||
if active_index == Some(*index) {
|
||||
theme.colors().search_active_match_background
|
||||
} else {
|
||||
theme.colors().search_match_background
|
||||
}
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn has_matches(&self) -> bool {
|
||||
self.active_match_index.is_some()
|
||||
}
|
||||
@@ -2456,7 +2475,9 @@ pub mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
use settings::{InlayHintSettingsContent, SettingsStore};
|
||||
use settings::{
|
||||
InlayHintSettingsContent, SettingsStore, ThemeColorsContent, ThemeStyleContent,
|
||||
};
|
||||
use util::{path, paths::PathStyle, rel_path::rel_path};
|
||||
use util_macros::perf;
|
||||
use workspace::DeploySearch;
|
||||
@@ -2464,8 +2485,105 @@ pub mod tests {
|
||||
#[perf]
|
||||
#[gpui::test]
|
||||
async fn test_project_search(cx: &mut TestAppContext) {
|
||||
fn dp(row: u32, col: u32) -> DisplayPoint {
|
||||
DisplayPoint::new(DisplayRow(row), col)
|
||||
}
|
||||
|
||||
fn assert_active_match_index(
|
||||
search_view: &WindowHandle<ProjectSearchView>,
|
||||
cx: &mut TestAppContext,
|
||||
expected_index: usize,
|
||||
) {
|
||||
search_view
|
||||
.update(cx, |search_view, _window, _cx| {
|
||||
assert_eq!(search_view.active_match_index, Some(expected_index));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn assert_selection_range(
|
||||
search_view: &WindowHandle<ProjectSearchView>,
|
||||
cx: &mut TestAppContext,
|
||||
expected_range: Range<DisplayPoint>,
|
||||
) {
|
||||
search_view
|
||||
.update(cx, |search_view, _window, cx| {
|
||||
assert_eq!(
|
||||
search_view.results_editor.update(cx, |editor, cx| editor
|
||||
.selections
|
||||
.display_ranges(&editor.display_snapshot(cx))),
|
||||
[expected_range]
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn assert_highlights(
|
||||
search_view: &WindowHandle<ProjectSearchView>,
|
||||
cx: &mut TestAppContext,
|
||||
expected_highlights: Vec<(Range<DisplayPoint>, &str)>,
|
||||
) {
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
let match_bg = cx.theme().colors().search_match_background;
|
||||
let active_match_bg = cx.theme().colors().search_active_match_background;
|
||||
let selection_bg = cx
|
||||
.theme()
|
||||
.colors()
|
||||
.editor_document_highlight_bracket_background;
|
||||
|
||||
let highlights: Vec<_> = expected_highlights
|
||||
.into_iter()
|
||||
.map(|(range, color_type)| {
|
||||
let color = match color_type {
|
||||
"active" => active_match_bg,
|
||||
"match" => match_bg,
|
||||
"selection" => selection_bg,
|
||||
_ => panic!("Unknown color type"),
|
||||
};
|
||||
(range, color)
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
search_view.results_editor.update(cx, |editor, cx| editor
|
||||
.all_text_background_highlights(window, cx)),
|
||||
highlights.as_slice()
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn select_match(
|
||||
search_view: &WindowHandle<ProjectSearchView>,
|
||||
cx: &mut TestAppContext,
|
||||
direction: Direction,
|
||||
) {
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
search_view.select_match(direction, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
init_test(cx);
|
||||
|
||||
// Override active search match color since the fallback theme uses the same color
|
||||
// for normal search match and active one, which can make this test less robust.
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |settings, cx| {
|
||||
settings.update_user_settings(cx, |settings| {
|
||||
settings.theme.experimental_theme_overrides = Some(ThemeStyleContent {
|
||||
colors: ThemeColorsContent {
|
||||
search_active_match_background: Some("#ff0000ff".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
@@ -2486,113 +2604,113 @@ pub mod tests {
|
||||
});
|
||||
|
||||
perform_search(search_view, "TWO", cx);
|
||||
search_view.update(cx, |search_view, window, cx| {
|
||||
assert_eq!(
|
||||
search_view
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;"
|
||||
);
|
||||
let match_background_color = cx.theme().colors().search_match_background;
|
||||
let selection_background_color = cx.theme().colors().editor_document_highlight_bracket_background;
|
||||
assert_eq!(
|
||||
search_view
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.all_text_background_highlights(window, cx)),
|
||||
&[
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35),
|
||||
match_background_color
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40),
|
||||
selection_background_color
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40),
|
||||
match_background_color
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9),
|
||||
match_background_color
|
||||
),
|
||||
|
||||
]
|
||||
);
|
||||
assert_eq!(search_view.active_match_index, Some(0));
|
||||
assert_eq!(
|
||||
search_view
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.selections.display_ranges(&editor.display_snapshot(cx))),
|
||||
[DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35)]
|
||||
);
|
||||
|
||||
search_view.select_match(Direction::Next, window, cx);
|
||||
}).unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
assert_eq!(search_view.active_match_index, Some(1));
|
||||
.update(cx, |search_view, _window, cx| {
|
||||
assert_eq!(
|
||||
search_view.results_editor.update(cx, |editor, cx| editor
|
||||
.selections
|
||||
.display_ranges(&editor.display_snapshot(cx))),
|
||||
[DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40)]
|
||||
);
|
||||
search_view.select_match(Direction::Next, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
assert_eq!(search_view.active_match_index, Some(2));
|
||||
assert_eq!(
|
||||
search_view.results_editor.update(cx, |editor, cx| editor
|
||||
.selections
|
||||
.display_ranges(&editor.display_snapshot(cx))),
|
||||
[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9)]
|
||||
);
|
||||
search_view.select_match(Direction::Next, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
assert_eq!(search_view.active_match_index, Some(0));
|
||||
assert_eq!(
|
||||
search_view.results_editor.update(cx, |editor, cx| editor
|
||||
.selections
|
||||
.display_ranges(&editor.display_snapshot(cx))),
|
||||
[DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35)]
|
||||
);
|
||||
search_view.select_match(Direction::Prev, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
search_view
|
||||
.update(cx, |search_view, window, cx| {
|
||||
assert_eq!(search_view.active_match_index, Some(2));
|
||||
assert_eq!(
|
||||
search_view.results_editor.update(cx, |editor, cx| editor
|
||||
.selections
|
||||
.display_ranges(&editor.display_snapshot(cx))),
|
||||
[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9)]
|
||||
);
|
||||
search_view.select_match(Direction::Prev, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
search_view
|
||||
.update(cx, |search_view, _, cx| {
|
||||
assert_eq!(search_view.active_match_index, Some(1));
|
||||
assert_eq!(
|
||||
search_view.results_editor.update(cx, |editor, cx| editor
|
||||
.selections
|
||||
.display_ranges(&editor.display_snapshot(cx))),
|
||||
[DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40)]
|
||||
search_view
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;"
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_active_match_index(&search_view, cx, 0);
|
||||
assert_selection_range(&search_view, cx, dp(2, 32)..dp(2, 35));
|
||||
assert_highlights(
|
||||
&search_view,
|
||||
cx,
|
||||
vec![
|
||||
(dp(2, 32)..dp(2, 35), "active"),
|
||||
(dp(2, 37)..dp(2, 40), "selection"),
|
||||
(dp(2, 37)..dp(2, 40), "match"),
|
||||
(dp(5, 6)..dp(5, 9), "match"),
|
||||
// TODO: we should be getting selection highlight here after project search
|
||||
// but for some reason we are not getting it here
|
||||
],
|
||||
);
|
||||
select_match(&search_view, cx, Direction::Next);
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_active_match_index(&search_view, cx, 1);
|
||||
assert_selection_range(&search_view, cx, dp(2, 37)..dp(2, 40));
|
||||
assert_highlights(
|
||||
&search_view,
|
||||
cx,
|
||||
vec![
|
||||
(dp(2, 32)..dp(2, 35), "selection"),
|
||||
(dp(2, 32)..dp(2, 35), "match"),
|
||||
(dp(2, 37)..dp(2, 40), "active"),
|
||||
(dp(5, 6)..dp(5, 9), "selection"),
|
||||
(dp(5, 6)..dp(5, 9), "match"),
|
||||
],
|
||||
);
|
||||
select_match(&search_view, cx, Direction::Next);
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_active_match_index(&search_view, cx, 2);
|
||||
assert_selection_range(&search_view, cx, dp(5, 6)..dp(5, 9));
|
||||
assert_highlights(
|
||||
&search_view,
|
||||
cx,
|
||||
vec![
|
||||
(dp(2, 32)..dp(2, 35), "selection"),
|
||||
(dp(2, 32)..dp(2, 35), "match"),
|
||||
(dp(2, 37)..dp(2, 40), "selection"),
|
||||
(dp(2, 37)..dp(2, 40), "match"),
|
||||
(dp(5, 6)..dp(5, 9), "active"),
|
||||
],
|
||||
);
|
||||
select_match(&search_view, cx, Direction::Next);
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_active_match_index(&search_view, cx, 0);
|
||||
assert_selection_range(&search_view, cx, dp(2, 32)..dp(2, 35));
|
||||
assert_highlights(
|
||||
&search_view,
|
||||
cx,
|
||||
vec![
|
||||
(dp(2, 32)..dp(2, 35), "active"),
|
||||
(dp(2, 37)..dp(2, 40), "selection"),
|
||||
(dp(2, 37)..dp(2, 40), "match"),
|
||||
(dp(5, 6)..dp(5, 9), "selection"),
|
||||
(dp(5, 6)..dp(5, 9), "match"),
|
||||
],
|
||||
);
|
||||
select_match(&search_view, cx, Direction::Prev);
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_active_match_index(&search_view, cx, 2);
|
||||
assert_selection_range(&search_view, cx, dp(5, 6)..dp(5, 9));
|
||||
assert_highlights(
|
||||
&search_view,
|
||||
cx,
|
||||
vec![
|
||||
(dp(2, 32)..dp(2, 35), "selection"),
|
||||
(dp(2, 32)..dp(2, 35), "match"),
|
||||
(dp(2, 37)..dp(2, 40), "selection"),
|
||||
(dp(2, 37)..dp(2, 40), "match"),
|
||||
(dp(5, 6)..dp(5, 9), "active"),
|
||||
],
|
||||
);
|
||||
select_match(&search_view, cx, Direction::Prev);
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_active_match_index(&search_view, cx, 1);
|
||||
assert_selection_range(&search_view, cx, dp(2, 37)..dp(2, 40));
|
||||
assert_highlights(
|
||||
&search_view,
|
||||
cx,
|
||||
vec![
|
||||
(dp(2, 32)..dp(2, 35), "selection"),
|
||||
(dp(2, 32)..dp(2, 35), "match"),
|
||||
(dp(2, 37)..dp(2, 40), "active"),
|
||||
(dp(5, 6)..dp(5, 9), "selection"),
|
||||
(dp(5, 6)..dp(5, 9), "match"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[perf]
|
||||
|
||||
@@ -570,6 +570,9 @@ pub struct ThemeColorsContent {
|
||||
#[serde(rename = "search.match_background")]
|
||||
pub search_match_background: Option<String>,
|
||||
|
||||
#[serde(rename = "search.active_match_background")]
|
||||
pub search_active_match_background: Option<String>,
|
||||
|
||||
#[serde(rename = "panel.background")]
|
||||
pub panel_background: Option<String>,
|
||||
|
||||
|
||||
@@ -152,14 +152,31 @@ pub struct PreviewTabsSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub enabled: Option<bool>,
|
||||
/// Whether to open tabs in preview mode when opened from the project panel with a single click.
|
||||
///
|
||||
/// Default: true
|
||||
pub enable_preview_from_project_panel: Option<bool>,
|
||||
/// Whether to open tabs in preview mode when selected from the file finder.
|
||||
///
|
||||
/// Default: false
|
||||
pub enable_preview_from_file_finder: Option<bool>,
|
||||
/// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
|
||||
/// Whether to open tabs in preview mode when opened from a multibuffer.
|
||||
///
|
||||
/// Default: true
|
||||
pub enable_preview_from_multibuffer: Option<bool>,
|
||||
/// Whether to open tabs in preview mode when code navigation is used to open a multibuffer.
|
||||
///
|
||||
/// Default: false
|
||||
pub enable_preview_from_code_navigation: Option<bool>,
|
||||
pub enable_preview_multibuffer_from_code_navigation: Option<bool>,
|
||||
/// Whether to open tabs in preview mode when code navigation is used to open a single file.
|
||||
///
|
||||
/// Default: true
|
||||
pub enable_preview_file_from_code_navigation: Option<bool>,
|
||||
/// Whether to keep tabs in preview mode when code navigation is used to navigate away from them.
|
||||
/// If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.
|
||||
///
|
||||
/// Default: false
|
||||
pub enable_keep_preview_on_code_navigation: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
||||
@@ -619,9 +619,13 @@ impl VsCodeSettings {
|
||||
fn preview_tabs_settings_content(&self) -> Option<PreviewTabsSettingsContent> {
|
||||
skip_default(PreviewTabsSettingsContent {
|
||||
enabled: self.read_bool("workbench.editor.enablePreview"),
|
||||
enable_preview_from_project_panel: None,
|
||||
enable_preview_from_file_finder: self
|
||||
.read_bool("workbench.editor.enablePreviewFromQuickOpen"),
|
||||
enable_preview_from_code_navigation: self
|
||||
enable_preview_from_multibuffer: None,
|
||||
enable_preview_multibuffer_from_code_navigation: None,
|
||||
enable_preview_file_from_code_navigation: None,
|
||||
enable_keep_preview_on_code_navigation: self
|
||||
.read_bool("workbench.editor.enablePreviewFromCodeNavigation"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3145,7 +3145,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
SettingsPageItem::SectionHeader("Preview Tabs"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Preview Tabs Enabled",
|
||||
description: "Show opened editors as Preview tabs.",
|
||||
description: "Show opened editors as preview tabs.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("preview_tabs.enabled"),
|
||||
pick: |settings_content| {
|
||||
@@ -3161,9 +3161,31 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Enable Preview From Project Panel",
|
||||
description: "Whether to open tabs in preview mode when opened from the project panel with a single click.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("preview_tabs.enable_preview_from_project_panel"),
|
||||
pick: |settings_content| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.as_ref()?
|
||||
.enable_preview_from_project_panel
|
||||
.as_ref()
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.get_or_insert_default()
|
||||
.enable_preview_from_project_panel = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Enable Preview From File Finder",
|
||||
description: "Whether to open tabs in Preview mode when selected from the file finder.",
|
||||
description: "Whether to open tabs in preview mode when selected from the file finder.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("preview_tabs.enable_preview_from_file_finder"),
|
||||
pick: |settings_content| {
|
||||
@@ -3184,22 +3206,88 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Enable Preview From Code Navigation",
|
||||
description: "Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.",
|
||||
title: "Enable Preview From Multibuffer",
|
||||
description: "Whether to open tabs in preview mode when opened from a multibuffer.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("preview_tabs.enable_preview_from_code_navigation"),
|
||||
json_path: Some("preview_tabs.enable_preview_from_multibuffer"),
|
||||
pick: |settings_content| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.as_ref()?
|
||||
.enable_preview_from_code_navigation
|
||||
.enable_preview_from_multibuffer
|
||||
.as_ref()
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.get_or_insert_default()
|
||||
.enable_preview_from_code_navigation = value;
|
||||
.enable_preview_from_multibuffer = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Enable Preview Multibuffer From Code Navigation",
|
||||
description: "Whether to open tabs in preview mode when code navigation is used to open a multibuffer.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("preview_tabs.enable_preview_multibuffer_from_code_navigation"),
|
||||
pick: |settings_content| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.as_ref()?
|
||||
.enable_preview_multibuffer_from_code_navigation
|
||||
.as_ref()
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.get_or_insert_default()
|
||||
.enable_preview_multibuffer_from_code_navigation = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Enable Preview File From Code Navigation",
|
||||
description: "Whether to open tabs in preview mode when code navigation is used to open a single file.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("preview_tabs.enable_preview_file_from_code_navigation"),
|
||||
pick: |settings_content| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.as_ref()?
|
||||
.enable_preview_file_from_code_navigation
|
||||
.as_ref()
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.get_or_insert_default()
|
||||
.enable_preview_file_from_code_navigation = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Enable Keep Preview On Code Navigation",
|
||||
description: "Whether to keep tabs in preview mode when code navigation is used to navigate away from them. If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("preview_tabs.enable_keep_preview_on_code_navigation"),
|
||||
pick: |settings_content| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.as_ref()?
|
||||
.enable_keep_preview_on_code_navigation
|
||||
.as_ref()
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content
|
||||
.preview_tabs
|
||||
.get_or_insert_default()
|
||||
.enable_keep_preview_on_code_navigation = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
|
||||
@@ -23,9 +23,9 @@ use ui::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
ModalView, Pane, SaveIntent, Workspace,
|
||||
Event as WorkspaceEvent, ModalView, Pane, SaveIntent, Workspace,
|
||||
item::{ItemHandle, ItemSettings, ShowDiagnostics, TabContentParams},
|
||||
pane::{Event as PaneEvent, render_item_indicator, tab_details},
|
||||
pane::{render_item_indicator, tab_details},
|
||||
};
|
||||
|
||||
const PANEL_WIDTH_REMS: f32 = 28.;
|
||||
@@ -322,7 +322,7 @@ impl TabSwitcherDelegate {
|
||||
cx: &mut Context<TabSwitcher>,
|
||||
original_items: Vec<(Entity<Pane>, usize)>,
|
||||
) -> Self {
|
||||
Self::subscribe_to_updates(&pane, window, cx);
|
||||
Self::subscribe_to_updates(&workspace, window, cx);
|
||||
Self {
|
||||
select_last,
|
||||
tab_switcher,
|
||||
@@ -338,34 +338,36 @@ impl TabSwitcherDelegate {
|
||||
}
|
||||
|
||||
fn subscribe_to_updates(
|
||||
pane: &WeakEntity<Pane>,
|
||||
workspace: &WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<TabSwitcher>,
|
||||
) {
|
||||
let Some(pane) = pane.upgrade() else {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
cx.subscribe_in(&pane, window, |tab_switcher, _, event, window, cx| {
|
||||
cx.subscribe_in(&workspace, window, |tab_switcher, _, event, window, cx| {
|
||||
match event {
|
||||
PaneEvent::AddItem { .. } | PaneEvent::Remove { .. } => {
|
||||
WorkspaceEvent::ItemAdded { .. } | WorkspaceEvent::PaneRemoved => {
|
||||
tab_switcher.picker.update(cx, |picker, cx| {
|
||||
let query = picker.query(cx);
|
||||
picker.delegate.update_matches(query, window, cx);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
PaneEvent::RemovedItem { .. } => tab_switcher.picker.update(cx, |picker, cx| {
|
||||
let query = picker.query(cx);
|
||||
picker.delegate.update_matches(query, window, cx);
|
||||
WorkspaceEvent::ItemRemoved { .. } => {
|
||||
tab_switcher.picker.update(cx, |picker, cx| {
|
||||
let query = picker.query(cx);
|
||||
picker.delegate.update_matches(query, window, cx);
|
||||
|
||||
// When the Tab Switcher is being used and an item is
|
||||
// removed, there's a chance that the new selected index
|
||||
// will not match the actual tab that is now being displayed
|
||||
// by the pane, as such, the selected index needs to be
|
||||
// updated to match the pane's state.
|
||||
picker.delegate.sync_selected_index(cx);
|
||||
cx.notify();
|
||||
}),
|
||||
// When the Tab Switcher is being used and an item is
|
||||
// removed, there's a chance that the new selected index
|
||||
// will not match the actual tab that is now being displayed
|
||||
// by the pane, as such, the selected index needs to be
|
||||
// updated to match the pane's state.
|
||||
picker.delegate.sync_selected_index(cx);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
})
|
||||
@@ -563,7 +565,14 @@ impl TabSwitcherDelegate {
|
||||
/// as the pane's active item can be indirectly updated and this method
|
||||
/// ensures that the picker can react to those changes.
|
||||
fn sync_selected_index(&mut self, cx: &mut Context<Picker<TabSwitcherDelegate>>) {
|
||||
let Ok(Some(item)) = self.pane.read_with(cx, |pane, _cx| pane.active_item()) else {
|
||||
let item = if self.is_all_panes {
|
||||
self.workspace
|
||||
.read_with(cx, |workspace, cx| workspace.active_item(cx))
|
||||
} else {
|
||||
self.pane.read_with(cx, |pane, _cx| pane.active_item())
|
||||
};
|
||||
|
||||
let Ok(Some(item)) = item else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -1434,6 +1434,7 @@ impl SearchableItem for TerminalView {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
matches: &[Self::Match],
|
||||
_active_match_index: Option<usize>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
|
||||
@@ -91,6 +91,7 @@ impl ThemeColors {
|
||||
tab_inactive_background: neutral().light().step_2(),
|
||||
tab_active_background: neutral().light().step_1(),
|
||||
search_match_background: neutral().light().step_5(),
|
||||
search_active_match_background: neutral().light().step_7(),
|
||||
panel_background: neutral().light().step_2(),
|
||||
panel_focused_border: blue().light().step_10(),
|
||||
panel_indent_guide: neutral().light_alpha().step_5(),
|
||||
@@ -228,6 +229,7 @@ impl ThemeColors {
|
||||
tab_inactive_background: neutral().dark().step_2(),
|
||||
tab_active_background: neutral().dark().step_1(),
|
||||
search_match_background: neutral().dark().step_5(),
|
||||
search_active_match_background: neutral().dark().step_3(),
|
||||
panel_background: neutral().dark().step_2(),
|
||||
panel_focused_border: blue().dark().step_8(),
|
||||
panel_indent_guide: neutral().dark_alpha().step_4(),
|
||||
|
||||
@@ -152,6 +152,7 @@ pub(crate) fn zed_default_dark() -> Theme {
|
||||
tab_inactive_background: bg,
|
||||
tab_active_background: editor,
|
||||
search_match_background: bg,
|
||||
search_active_match_background: bg,
|
||||
|
||||
editor_background: editor,
|
||||
editor_gutter_background: editor,
|
||||
|
||||
@@ -287,6 +287,15 @@ pub fn theme_colors_refinement(
|
||||
.panel_background
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok());
|
||||
let search_match_background = this
|
||||
.search_match_background
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok());
|
||||
let search_active_match_background = this
|
||||
.search_active_match_background
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok())
|
||||
.or(search_match_background);
|
||||
ThemeColorsRefinement {
|
||||
border,
|
||||
border_variant: this
|
||||
@@ -442,10 +451,8 @@ pub fn theme_colors_refinement(
|
||||
.tab_active_background
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok()),
|
||||
search_match_background: this
|
||||
.search_match_background
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok()),
|
||||
search_match_background: search_match_background,
|
||||
search_active_match_background: search_active_match_background,
|
||||
panel_background,
|
||||
panel_focused_border: this
|
||||
.panel_focused_border
|
||||
|
||||
@@ -128,6 +128,7 @@ pub struct ThemeColors {
|
||||
pub tab_inactive_background: Hsla,
|
||||
pub tab_active_background: Hsla,
|
||||
pub search_match_background: Hsla,
|
||||
pub search_active_match_background: Hsla,
|
||||
pub panel_background: Hsla,
|
||||
pub panel_focused_border: Hsla,
|
||||
pub panel_indent_guide: Hsla,
|
||||
@@ -352,6 +353,7 @@ pub enum ThemeColorField {
|
||||
TabInactiveBackground,
|
||||
TabActiveBackground,
|
||||
SearchMatchBackground,
|
||||
SearchActiveMatchBackground,
|
||||
PanelBackground,
|
||||
PanelFocusedBorder,
|
||||
PanelIndentGuide,
|
||||
@@ -467,6 +469,7 @@ impl ThemeColors {
|
||||
ThemeColorField::TabInactiveBackground => self.tab_inactive_background,
|
||||
ThemeColorField::TabActiveBackground => self.tab_active_background,
|
||||
ThemeColorField::SearchMatchBackground => self.search_match_background,
|
||||
ThemeColorField::SearchActiveMatchBackground => self.search_active_match_background,
|
||||
ThemeColorField::PanelBackground => self.panel_background,
|
||||
ThemeColorField::PanelFocusedBorder => self.panel_focused_border,
|
||||
ThemeColorField::PanelIndentGuide => self.panel_indent_guide,
|
||||
|
||||
@@ -227,7 +227,7 @@ impl Vim {
|
||||
|
||||
editor.highlight_background::<HighlightOnYank>(
|
||||
&ranges_to_highlight,
|
||||
|colors| colors.colors().editor_document_highlight_read_background,
|
||||
|_, colors| colors.colors().editor_document_highlight_read_background,
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |this, cx| {
|
||||
|
||||
@@ -273,7 +273,7 @@ impl Vim {
|
||||
let ranges = [new_range];
|
||||
editor.highlight_background::<VimExchange>(
|
||||
&ranges,
|
||||
|theme| theme.colors().editor_document_highlight_read_background,
|
||||
|_, theme| theme.colors().editor_document_highlight_read_background,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,8 +64,12 @@ pub struct ItemSettings {
|
||||
#[derive(RegisterSetting)]
|
||||
pub struct PreviewTabsSettings {
|
||||
pub enabled: bool,
|
||||
pub enable_preview_from_project_panel: bool,
|
||||
pub enable_preview_from_file_finder: bool,
|
||||
pub enable_preview_from_code_navigation: bool,
|
||||
pub enable_preview_from_multibuffer: bool,
|
||||
pub enable_preview_multibuffer_from_code_navigation: bool,
|
||||
pub enable_preview_file_from_code_navigation: bool,
|
||||
pub enable_keep_preview_on_code_navigation: bool,
|
||||
}
|
||||
|
||||
impl Settings for ItemSettings {
|
||||
@@ -87,9 +91,19 @@ impl Settings for PreviewTabsSettings {
|
||||
let preview_tabs = content.preview_tabs.as_ref().unwrap();
|
||||
Self {
|
||||
enabled: preview_tabs.enabled.unwrap(),
|
||||
enable_preview_from_project_panel: preview_tabs
|
||||
.enable_preview_from_project_panel
|
||||
.unwrap(),
|
||||
enable_preview_from_file_finder: preview_tabs.enable_preview_from_file_finder.unwrap(),
|
||||
enable_preview_from_code_navigation: preview_tabs
|
||||
.enable_preview_from_code_navigation
|
||||
enable_preview_from_multibuffer: preview_tabs.enable_preview_from_multibuffer.unwrap(),
|
||||
enable_preview_multibuffer_from_code_navigation: preview_tabs
|
||||
.enable_preview_multibuffer_from_code_navigation
|
||||
.unwrap(),
|
||||
enable_preview_file_from_code_navigation: preview_tabs
|
||||
.enable_preview_file_from_code_navigation
|
||||
.unwrap(),
|
||||
enable_keep_preview_on_code_navigation: preview_tabs
|
||||
.enable_keep_preview_on_code_navigation
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,10 +873,35 @@ impl Pane {
|
||||
self.preview_item_id == Some(item_id)
|
||||
}
|
||||
|
||||
/// Promotes the item with the given ID to not be a preview item.
|
||||
/// This does nothing if it wasn't already a preview item.
|
||||
pub fn unpreview_item_if_preview(&mut self, item_id: EntityId) {
|
||||
if self.is_active_preview_item(item_id) {
|
||||
self.preview_item_id = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the item with the given ID as the preview item.
|
||||
/// This will be ignored if the global setting `preview_tabs` is disabled.
|
||||
pub fn set_preview_item_id(&mut self, item_id: Option<EntityId>, cx: &App) {
|
||||
if PreviewTabsSettings::get_global(cx).enabled {
|
||||
///
|
||||
/// The old preview item (if there was one) is closed and its index is returned.
|
||||
pub fn replace_preview_item_id(
|
||||
&mut self,
|
||||
item_id: EntityId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<usize> {
|
||||
let idx = self.close_current_preview_item(window, cx);
|
||||
self.set_preview_item_id(Some(item_id), cx);
|
||||
idx
|
||||
}
|
||||
|
||||
/// Marks the item with the given ID as the preview item.
|
||||
/// This will be ignored if the global setting `preview_tabs` is disabled.
|
||||
///
|
||||
/// This is a low-level method. Prefer `unpreview_item_if_preview()` or `set_new_preview_item()`.
|
||||
pub(crate) fn set_preview_item_id(&mut self, item_id: Option<EntityId>, cx: &App) {
|
||||
if item_id.is_none() || PreviewTabsSettings::get_global(cx).enabled {
|
||||
self.preview_item_id = item_id;
|
||||
}
|
||||
}
|
||||
@@ -895,7 +920,7 @@ impl Pane {
|
||||
&& preview_item.item_id() == item_id
|
||||
&& !preview_item.preserve_preview(cx)
|
||||
{
|
||||
self.set_preview_item_id(None, cx);
|
||||
self.unpreview_item_if_preview(item_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,14 +961,8 @@ impl Pane {
|
||||
|
||||
let set_up_existing_item =
|
||||
|index: usize, pane: &mut Self, window: &mut Window, cx: &mut Context<Self>| {
|
||||
// If the item is already open, and the item is a preview item
|
||||
// and we are not allowing items to open as preview, mark the item as persistent.
|
||||
if let Some(preview_item_id) = pane.preview_item_id
|
||||
&& let Some(tab) = pane.items.get(index)
|
||||
&& tab.item_id() == preview_item_id
|
||||
&& !allow_preview
|
||||
{
|
||||
pane.set_preview_item_id(None, cx);
|
||||
if !allow_preview && let Some(item) = pane.items.get(index) {
|
||||
pane.unpreview_item_if_preview(item.item_id());
|
||||
}
|
||||
if activate {
|
||||
pane.activate_item(index, focus_item, focus_item, window, cx);
|
||||
@@ -955,7 +974,7 @@ impl Pane {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>| {
|
||||
if allow_preview {
|
||||
pane.set_preview_item_id(Some(new_item.item_id()), cx);
|
||||
pane.replace_preview_item_id(new_item.item_id(), window, cx);
|
||||
}
|
||||
|
||||
if let Some(text) = new_item.telemetry_event_text(cx) {
|
||||
@@ -1036,6 +1055,7 @@ impl Pane {
|
||||
) -> Option<usize> {
|
||||
let item_idx = self.preview_item_idx()?;
|
||||
let id = self.preview_item_id()?;
|
||||
self.set_preview_item_id(None, cx);
|
||||
|
||||
let prev_active_item_index = self.active_item_index;
|
||||
self.remove_item(id, false, false, window, cx);
|
||||
@@ -1981,9 +2001,7 @@ impl Pane {
|
||||
item.on_removed(cx);
|
||||
self.nav_history.set_mode(mode);
|
||||
|
||||
if self.is_active_preview_item(item.item_id()) {
|
||||
self.set_preview_item_id(None, cx);
|
||||
}
|
||||
self.unpreview_item_if_preview(item.item_id());
|
||||
|
||||
if let Some(path) = item.project_path(cx) {
|
||||
let abs_path = self
|
||||
@@ -2194,9 +2212,7 @@ impl Pane {
|
||||
|
||||
if can_save {
|
||||
pane.update_in(cx, |pane, window, cx| {
|
||||
if pane.is_active_preview_item(item.item_id()) {
|
||||
pane.set_preview_item_id(None, cx);
|
||||
}
|
||||
pane.unpreview_item_if_preview(item.item_id());
|
||||
item.save(
|
||||
SaveOptions {
|
||||
format: should_format,
|
||||
@@ -2450,8 +2466,8 @@ impl Pane {
|
||||
let id = self.item_for_index(ix)?.item_id();
|
||||
let should_activate = ix == self.active_item_index;
|
||||
|
||||
if matches!(operation, PinOperation::Pin) && self.is_active_preview_item(id) {
|
||||
self.set_preview_item_id(None, cx);
|
||||
if matches!(operation, PinOperation::Pin) {
|
||||
self.unpreview_item_if_preview(id);
|
||||
}
|
||||
|
||||
match operation {
|
||||
@@ -2624,12 +2640,9 @@ impl Pane {
|
||||
)
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |pane, event: &MouseDownEvent, _, cx| {
|
||||
if let Some(id) = pane.preview_item_id
|
||||
&& id == item_id
|
||||
&& event.click_count > 1
|
||||
{
|
||||
pane.set_preview_item_id(None, cx);
|
||||
cx.listener(move |pane, event: &MouseDownEvent, _, _| {
|
||||
if event.click_count > 1 {
|
||||
pane.unpreview_item_if_preview(item_id);
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -3272,11 +3285,7 @@ impl Pane {
|
||||
let mut to_pane = cx.entity();
|
||||
let split_direction = self.drag_split_direction;
|
||||
let item_id = dragged_tab.item.item_id();
|
||||
if let Some(preview_item_id) = self.preview_item_id
|
||||
&& item_id == preview_item_id
|
||||
{
|
||||
self.set_preview_item_id(None, cx);
|
||||
}
|
||||
self.unpreview_item_if_preview(item_id);
|
||||
|
||||
let is_clone = cfg!(target_os = "macos") && window.modifiers().alt
|
||||
|| cfg!(not(target_os = "macos")) && window.modifiers().control;
|
||||
@@ -3788,15 +3797,17 @@ impl Render for Pane {
|
||||
.on_action(cx.listener(Self::toggle_pin_tab))
|
||||
.on_action(cx.listener(Self::unpin_all_tabs))
|
||||
.when(PreviewTabsSettings::get_global(cx).enabled, |this| {
|
||||
this.on_action(cx.listener(|pane: &mut Pane, _: &TogglePreviewTab, _, cx| {
|
||||
if let Some(active_item_id) = pane.active_item().map(|i| i.item_id()) {
|
||||
if pane.is_active_preview_item(active_item_id) {
|
||||
pane.set_preview_item_id(None, cx);
|
||||
} else {
|
||||
pane.set_preview_item_id(Some(active_item_id), cx);
|
||||
this.on_action(
|
||||
cx.listener(|pane: &mut Pane, _: &TogglePreviewTab, window, cx| {
|
||||
if let Some(active_item_id) = pane.active_item().map(|i| i.item_id()) {
|
||||
if pane.is_active_preview_item(active_item_id) {
|
||||
pane.unpreview_item_if_preview(active_item_id);
|
||||
} else {
|
||||
pane.replace_preview_item_id(active_item_id, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}),
|
||||
)
|
||||
})
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &CloseActiveItem, window, cx| {
|
||||
|
||||
@@ -96,6 +96,7 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
matches: &[Self::Match],
|
||||
active_match_index: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
);
|
||||
@@ -179,7 +180,13 @@ pub trait SearchableItemHandle: ItemHandle {
|
||||
handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
|
||||
) -> Subscription;
|
||||
fn clear_matches(&self, window: &mut Window, cx: &mut App);
|
||||
fn update_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
|
||||
fn update_matches(
|
||||
&self,
|
||||
matches: &AnyVec<dyn Send>,
|
||||
active_match_index: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
);
|
||||
fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
|
||||
fn activate_match(
|
||||
&self,
|
||||
@@ -264,10 +271,16 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
|
||||
fn clear_matches(&self, window: &mut Window, cx: &mut App) {
|
||||
self.update(cx, |this, cx| this.clear_matches(window, cx));
|
||||
}
|
||||
fn update_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
|
||||
fn update_matches(
|
||||
&self,
|
||||
matches: &AnyVec<dyn Send>,
|
||||
active_match_index: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let matches = matches.downcast_ref().unwrap();
|
||||
self.update(cx, |this, cx| {
|
||||
this.update_matches(matches.as_slice(), window, cx)
|
||||
this.update_matches(matches.as_slice(), active_match_index, window, cx)
|
||||
});
|
||||
}
|
||||
fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
|
||||
|
||||
@@ -3636,14 +3636,33 @@ impl Workspace {
|
||||
project_item: Entity<T::Item>,
|
||||
activate_pane: bool,
|
||||
focus_item: bool,
|
||||
keep_old_preview: bool,
|
||||
allow_new_preview: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<T>
|
||||
where
|
||||
T: ProjectItem,
|
||||
{
|
||||
let old_item_id = pane.read(cx).active_item().map(|item| item.item_id());
|
||||
|
||||
if let Some(item) = self.find_project_item(&pane, &project_item, cx) {
|
||||
if !keep_old_preview
|
||||
&& let Some(old_id) = old_item_id
|
||||
&& old_id != item.item_id()
|
||||
{
|
||||
// switching to a different item, so unpreview old active item
|
||||
pane.update(cx, |pane, _| {
|
||||
pane.unpreview_item_if_preview(old_id);
|
||||
});
|
||||
}
|
||||
|
||||
self.activate_item(&item, activate_pane, focus_item, window, cx);
|
||||
if !allow_new_preview {
|
||||
pane.update(cx, |pane, _| {
|
||||
pane.unpreview_item_if_preview(item.item_id());
|
||||
});
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -3652,16 +3671,14 @@ impl Workspace {
|
||||
T::for_project_item(self.project().clone(), Some(pane), project_item, window, cx)
|
||||
})
|
||||
});
|
||||
let item_id = item.item_id();
|
||||
let mut destination_index = None;
|
||||
pane.update(cx, |pane, cx| {
|
||||
if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation
|
||||
&& let Some(preview_item_id) = pane.preview_item_id()
|
||||
&& preview_item_id != item_id
|
||||
{
|
||||
destination_index = pane.close_current_preview_item(window, cx);
|
||||
if !keep_old_preview && let Some(old_id) = old_item_id {
|
||||
pane.unpreview_item_if_preview(old_id);
|
||||
}
|
||||
if allow_new_preview {
|
||||
destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
|
||||
}
|
||||
pane.set_preview_item_id(Some(item.item_id()), cx)
|
||||
});
|
||||
|
||||
self.add_item(
|
||||
@@ -11457,4 +11474,26 @@ mod tests {
|
||||
});
|
||||
item
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_window_focus_set_on_startup_without_buffer(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/root", json!({"a": ""})).await;
|
||||
let project = Project::test(fs, ["/root".as_ref()], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
workspace.update_in(cx, |_workspace, window, cx| {
|
||||
let focused = window.focused(cx);
|
||||
assert!(
|
||||
focused.is_some(),
|
||||
"Expected window focus to be set on startup, but it was None"
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2861,11 +2861,25 @@ Configuration object for defining settings profiles. Example:
|
||||
```json [settings]
|
||||
"preview_tabs": {
|
||||
"enabled": true,
|
||||
"enable_preview_from_project_panel": true,
|
||||
"enable_preview_from_file_finder": false,
|
||||
"enable_preview_from_code_navigation": false,
|
||||
"enable_preview_from_multibuffer": true,
|
||||
"enable_preview_multibuffer_from_code_navigation": false,
|
||||
"enable_preview_file_from_code_navigation": true,
|
||||
"enable_keep_preview_on_code_navigation": false,
|
||||
}
|
||||
```
|
||||
|
||||
### Enable preview from project panel
|
||||
|
||||
- Description: Determines whether to open files in preview mode when opened from the project panel with a single click.
|
||||
- Setting: `enable_preview_from_project_panel`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Enable preview from file finder
|
||||
|
||||
- Description: Determines whether to open files in preview mode when selected from the file finder.
|
||||
@@ -2876,10 +2890,40 @@ Configuration object for defining settings profiles. Example:
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Enable preview from code navigation
|
||||
### Enable preview from multibuffer
|
||||
|
||||
- Description: Determines whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
|
||||
- Setting: `enable_preview_from_code_navigation`
|
||||
- Description: Determines whether to open files in preview mode when opened from a multibuffer.
|
||||
- Setting: `enable_preview_from_multibuffer`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Enable preview multibuffer from code navigation
|
||||
|
||||
- Description: Determines whether to open tabs in preview mode when code navigation is used to open a multibuffer.
|
||||
- Setting: `enable_preview_multibuffer_from_code_navigation`
|
||||
- Default: `false`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Enable preview file from code navigation
|
||||
|
||||
- Description: Determines whether to open tabs in preview mode when code navigation is used to open a single file.
|
||||
- Setting: `enable_preview_file_from_code_navigation`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Enable keep preview on code navigation
|
||||
|
||||
- Description: Determines whether to keep tabs in preview mode when code navigation is used to navigate away from them. If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.
|
||||
- Setting: `enable_keep_preview_on_code_navigation`
|
||||
- Default: `false`
|
||||
|
||||
**Options**
|
||||
|
||||
Reference in New Issue
Block a user