diff --git a/.github/actions/run_tests_windows/action.yml b/.github/actions/run_tests_windows/action.yml index e3e3b7142e..0a550c7d32 100644 --- a/.github/actions/run_tests_windows/action.yml +++ b/.github/actions/run_tests_windows/action.yml @@ -56,7 +56,6 @@ runs: $env:COMPlus_CreateDumpDiagnostics = "1" cargo nextest run --workspace --no-fail-fast - continue-on-error: true - name: Analyze crash dumps if: always() diff --git a/Cargo.lock b/Cargo.lock index f44f513f78..6dbbccedaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,7 +7,6 @@ name = "acp_thread" version = "0.1.0" dependencies = [ "action_log", - "agent", "agent-client-protocol", "anyhow", "buffer_diff", @@ -20,6 +19,7 @@ dependencies = [ "indoc", "itertools 0.14.0", "language", + "language_model", "markdown", "parking_lot", "project", @@ -130,7 +130,6 @@ dependencies = [ "component", "context_server", "convert_case 0.8.0", - "feature_flags", "fs", "futures 0.3.31", "git", @@ -191,10 +190,12 @@ version = "0.1.0" dependencies = [ "acp_thread", "action_log", + "agent", "agent-client-protocol", "agent_servers", "agent_settings", "anyhow", + "assistant_context", "assistant_tool", "assistant_tools", "chrono", @@ -204,10 +205,12 @@ dependencies = [ "collections", "context_server", "ctor", + "db", "editor", "env_logger 0.11.8", "fs", "futures 0.3.31", + "git", "gpui", "gpui_tokio", "handlebars 4.5.0", @@ -221,6 +224,7 @@ dependencies = [ "log", "lsp", "open", + "parking_lot", "paths", "portable-pty", "pretty_assertions", @@ -233,6 +237,7 @@ dependencies = [ "serde_json", "settings", "smol", + "sqlez", "task", "tempfile", "terminal", @@ -249,6 +254,7 @@ dependencies = [ "workspace-hack", "worktree", "zlog", + "zstd", ] [[package]] @@ -256,7 +262,9 @@ name = "agent_servers" version = "0.1.0" dependencies = [ "acp_thread", + "action_log", "agent-client-protocol", + "agent_settings", "agentic-coding-protocol", "anyhow", "collections", @@ -267,6 +275,8 @@ dependencies = [ "indoc", "itertools 0.14.0", "language", + "language_model", + "language_models", "libc", "log", "nix 0.29.0", @@ -274,6 +284,7 @@ dependencies = [ "project", "rand 0.8.5", "schemars", + "semver", "serde", "serde_json", "settings", @@ -3070,6 +3081,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "serde_urlencoded", "settings", "sha2", "smol", @@ -3861,7 +3873,7 @@ dependencies = [ "jni", "js-sys", "libc", - "mach2", + "mach2 0.4.2", "ndk", "ndk-context", "num-derive", @@ -4011,7 +4023,7 @@ checksum = "031ed29858d90cfdf27fe49fae28028a1f20466db97962fa2f4ea34809aeebf3" dependencies = [ "cfg-if", "libc", - "mach2", + "mach2 0.4.2", ] [[package]] @@ -4023,7 +4035,7 @@ dependencies = [ "cfg-if", "crash-context", "libc", - "mach2", + "mach2 0.4.2", "parking_lot", ] @@ -4033,6 +4045,7 @@ version = "0.1.0" dependencies = [ "crash-handler", "log", + "mach2 0.5.0", "minidumper", "paths", "release_channel", @@ -7477,6 +7490,7 @@ dependencies = [ "slotmap", "smallvec", "smol", + "stacksafe", "strum 0.27.1", "sum_tree", "taffy", @@ -9854,6 +9868,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -10190,7 +10213,7 @@ dependencies = [ "goblin", "libc", "log", - "mach2", + "mach2 0.4.2", "memmap2", "memoffset", "minidump-common", @@ -15536,6 +15559,40 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "stacksafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9c1172965d317e87ddb6d364a040d958b40a1db82b6ef97da26253a8b3d090" +dependencies = [ + "stacker", + "stacksafe-macro", +] + +[[package]] +name = "stacksafe-macro" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172175341049678163e979d9107ca3508046d4d2a7c6682bee46ac541b17db69" +dependencies = [ + "proc-macro-error2", + "quote", + "syn 2.0.101", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -18247,7 +18304,7 @@ dependencies = [ "indexmap", "libc", "log", - "mach2", + "mach2 0.4.2", "memfd", "object", "once_cell", @@ -20197,8 +20254,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yawc" -version = "0.2.4" -source = "git+https://github.com/deviant-forks/yawc?rev=1899688f3e69ace4545aceb97b2a13881cf26142#1899688f3e69ace4545aceb97b2a13881cf26142" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a5d82922135b4ae73a079a4ffb5501e9aadb4d785b8c660eaa0a8b899028c5" dependencies = [ "base64 0.22.1", "bytes 1.10.1", diff --git a/Cargo.toml b/Cargo.toml index 14691cf8a4..dc14c8ebd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -515,6 +515,7 @@ libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } linkify = "0.10.0" log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "39f629bdd03d59abd786ed9fc27e8bca02c0c0ec" } +mach2 = "0.5" markup5ever_rcdom = "0.3.0" metal = "0.29" minidumper = "0.8" @@ -582,6 +583,7 @@ serde_json_lenient = { version = "0.2", features = [ "raw_value", ] } serde_repr = "0.1" +serde_urlencoded = "0.7" sha2 = "0.10" shellexpand = "2.1.0" shlex = "1.3.0" @@ -589,6 +591,7 @@ simplelog = "0.12.2" smallvec = { version = "1.6", features = ["union"] } smol = "2.0" sqlformat = "0.2" +stacksafe = "0.1" streaming-iterator = "0.1" strsim = "0.11" strum = { version = "0.27.0", features = ["derive"] } @@ -659,9 +662,7 @@ which = "6.0.0" windows-core = "0.61" wit-component = "0.221" workspace-hack = "0.1.0" -# We can switch back to the published version once https://github.com/infinitefield/yawc/pull/16 is merged and a new -# version is released. -yawc = { git = "https://github.com/deviant-forks/yawc", rev = "1899688f3e69ace4545aceb97b2a13881cf26142" } +yawc = "0.2.5" zstd = "0.11" [workspace.dependencies.windows] @@ -821,10 +822,30 @@ single_range_in_vec_init = "allow" style = { level = "allow", priority = -1 } # Temporary list of style lints that we've fixed so far. +comparison_to_empty = "warn" +into_iter_on_ref = "warn" +iter_cloned_collect = "warn" +iter_next_slice = "warn" +iter_nth = "warn" +iter_nth_zero = "warn" +iter_skip_next = "warn" +let_and_return = "warn" module_inception = { level = "deny" } question_mark = { level = "deny" } +single_match = "warn" redundant_closure = { level = "deny" } +redundant_static_lifetimes = { level = "warn" } +redundant_pattern_matching = "warn" +redundant_field_names = "warn" declare_interior_mutable_const = { level = "deny" } +collapsible_if = { level = "warn"} +collapsible_else_if = { level = "warn" } +needless_borrow = { level = "warn"} +needless_return = { level = "warn" } +unnecessary_mut_passed = {level = "warn"} +unnecessary_map_or = { level = "warn" } +unused_unit = "warn" + # Individual rules that have violations in the codebase: type_complexity = "allow" # We often return trait objects from `new` functions. @@ -833,6 +854,8 @@ new_ret_no_self = { level = "allow" } # compared to Iterator::next. Yet, clippy complains about those. should_implement_trait = { level = "allow" } let_underscore_future = "allow" +# It doesn't make sense to implement `Default` unilaterally. +new_without_default = "allow" # in Rust it can be very tedious to reduce argument count without # running afoul of the borrow checker. @@ -841,6 +864,10 @@ too_many_arguments = "allow" # We often have large enum variants yet we rarely actually bother with splitting them up. large_enum_variant = "allow" +# `enum_variant_names` fires for all enums, even when they derive serde traits. +# Adhering to this lint would be a breaking change. +enum_variant_names = "allow" + [workspace.metadata.cargo-machete] ignored = [ "bindgen", diff --git a/assets/icons/menu_alt.svg b/assets/icons/menu_alt.svg index f73102e286..87add13216 100644 --- a/assets/icons/menu_alt.svg +++ b/assets/icons/menu_alt.svg @@ -1 +1,3 @@ - + + + diff --git a/assets/icons/menu_alt_temp.svg b/assets/icons/menu_alt_temp.svg new file mode 100644 index 0000000000..87add13216 --- /dev/null +++ b/assets/icons/menu_alt_temp.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/x_circle_filled.svg b/assets/icons/x_circle_filled.svg new file mode 100644 index 0000000000..52215acda8 --- /dev/null +++ b/assets/icons/x_circle_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/zed_agent.svg b/assets/icons/zed_agent.svg new file mode 100644 index 0000000000..b6e120a0b6 --- /dev/null +++ b/assets/icons/zed_agent.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 01c0b4e969..b4efa70572 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -327,7 +327,7 @@ } }, { - "context": "AcpThread > Editor", + "context": "AcpThread > Editor && !use_modifier_to_send", "use_key_equivalents": true, "bindings": { "enter": "agent::Chat", @@ -336,6 +336,16 @@ "ctrl-shift-n": "agent::RejectAll" } }, + { + "context": "AcpThread > Editor && use_modifier_to_send", + "use_key_equivalents": true, + "bindings": { + "ctrl-enter": "agent::Chat", + "shift-ctrl-r": "agent::OpenAgentDiff", + "ctrl-shift-y": "agent::KeepAll", + "ctrl-shift-n": "agent::RejectAll" + } + }, { "context": "ThreadHistory", "bindings": { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index e5b7fff9e1..ad2ab2ba89 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -379,7 +379,7 @@ } }, { - "context": "AcpThread > Editor", + "context": "AcpThread > Editor && !use_modifier_to_send", "use_key_equivalents": true, "bindings": { "enter": "agent::Chat", @@ -388,6 +388,16 @@ "cmd-shift-n": "agent::RejectAll" } }, + { + "context": "AcpThread > Editor && use_modifier_to_send", + "use_key_equivalents": true, + "bindings": { + "cmd-enter": "agent::Chat", + "shift-ctrl-r": "agent::OpenAgentDiff", + "cmd-shift-y": "agent::KeepAll", + "cmd-shift-n": "agent::RejectAll" + } + }, { "context": "ThreadHistory", "bindings": { diff --git a/assets/settings/default.json b/assets/settings/default.json index 72e4dcbf4f..c290baf003 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -717,7 +717,7 @@ // Can be 'never', 'always', or 'when_in_call', // or a boolean (interpreted as 'never'/'always'). "button": "when_in_call", - // Where to the chat panel. Can be 'left' or 'right'. + // Where to dock the chat panel. Can be 'left' or 'right'. "dock": "right", // Default width of the chat panel. "default_width": 240 @@ -725,7 +725,7 @@ "git_panel": { // Whether to show the git panel button in the status bar. "button": true, - // Where to show the git panel. Can be 'left' or 'right'. + // Where to dock the git panel. Can be 'left' or 'right'. "dock": "left", // Default width of the git panel. "default_width": 360, diff --git a/crates/acp_thread/Cargo.toml b/crates/acp_thread/Cargo.toml index 2b9a6513c8..eab756db51 100644 --- a/crates/acp_thread/Cargo.toml +++ b/crates/acp_thread/Cargo.toml @@ -18,7 +18,6 @@ test-support = ["gpui/test-support", "project/test-support", "dep:parking_lot"] [dependencies] action_log.workspace = true agent-client-protocol.workspace = true -agent.workspace = true anyhow.workspace = true buffer_diff.workspace = true collections.workspace = true @@ -28,6 +27,7 @@ futures.workspace = true gpui.workspace = true itertools.workspace = true language.workspace = true +language_model.workspace = true markdown.workspace = true parking_lot = { workspace = true, optional = true } project.workspace = true diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index fb31265326..5d3b35d018 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -3,9 +3,13 @@ mod diff; mod mention; mod terminal; +use collections::HashSet; pub use connection::*; pub use diff::*; +use language::language_settings::FormatOnSave; pub use mention::*; +use project::lsp_store::{FormatTrigger, LspFormatTarget}; +use serde::{Deserialize, Serialize}; pub use terminal::*; use action_log::ActionLog; @@ -24,6 +28,7 @@ use std::fmt::{Formatter, Write}; use std::ops::Range; use std::process::ExitStatus; use std::rc::Rc; +use std::time::{Duration, Instant}; use std::{fmt::Display, mem, path::PathBuf, sync::Arc}; use ui::App; use util::ResultExt; @@ -48,7 +53,7 @@ impl UserMessage { if self .checkpoint .as_ref() - .map_or(false, |checkpoint| checkpoint.show) + .is_some_and(|checkpoint| checkpoint.show) { writeln!(markdown, "## User (checkpoint)").unwrap(); } else { @@ -248,14 +253,13 @@ impl ToolCall { } if let Some(raw_output) = raw_output { - if self.content.is_empty() { - if let Some(markdown) = markdown_for_raw_output(&raw_output, &language_registry, cx) - { - self.content - .push(ToolCallContent::ContentBlock(ContentBlock::Markdown { - markdown, - })); - } + if self.content.is_empty() + && let Some(markdown) = markdown_for_raw_output(&raw_output, &language_registry, cx) + { + self.content + .push(ToolCallContent::ContentBlock(ContentBlock::Markdown { + markdown, + })); } self.raw_output = Some(raw_output); } @@ -429,11 +433,11 @@ impl ContentBlock { language_registry: &Arc, cx: &mut App, ) { - if matches!(self, ContentBlock::Empty) { - if let acp::ContentBlock::ResourceLink(resource_link) = block { - *self = ContentBlock::ResourceLink { resource_link }; - return; - } + if matches!(self, ContentBlock::Empty) + && let acp::ContentBlock::ResourceLink(resource_link) = block + { + *self = ContentBlock::ResourceLink { resource_link }; + return; } let new_content = self.block_string_contents(block); @@ -485,7 +489,7 @@ impl ContentBlock { } fn resource_link_md(uri: &str) -> String { - if let Some(uri) = MentionUri::parse(&uri).log_err() { + if let Some(uri) = MentionUri::parse(uri).log_err() { uri.as_link().to_string() } else { uri.to_string() @@ -537,9 +541,15 @@ impl ToolCallContent { acp::ToolCallContent::Content { content } => { Self::ContentBlock(ContentBlock::new(content, &language_registry, cx)) } - acp::ToolCallContent::Diff { diff } => { - Self::Diff(cx.new(|cx| Diff::from_acp(diff, language_registry, cx))) - } + acp::ToolCallContent::Diff { diff } => Self::Diff(cx.new(|cx| { + Diff::finalized( + diff.path, + diff.old_text, + diff.new_text, + language_registry, + cx, + ) + })), } } @@ -658,6 +668,21 @@ impl PlanEntry { } } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TokenUsage { + pub max_tokens: u64, + pub used_tokens: u64, +} + +#[derive(Debug, Clone)] +pub struct RetryStatus { + pub last_error: SharedString, + pub attempt: usize, + pub max_attempts: usize, + pub started_at: Instant, + pub duration: Duration, +} + pub struct AcpThread { title: SharedString, entries: Vec, @@ -668,16 +693,21 @@ pub struct AcpThread { send_task: Option>, connection: Rc, session_id: acp::SessionId, + token_usage: Option, } +#[derive(Debug)] pub enum AcpThreadEvent { NewEntry, + TitleUpdated, + TokenUsageUpdated, EntryUpdated(usize), EntriesRemoved(Range), ToolAuthorizationRequired, + Retry(RetryStatus), Stopped, Error, - ServerExited(ExitStatus), + LoadError(LoadError), } impl EventEmitter for AcpThread {} @@ -691,20 +721,30 @@ pub enum ThreadStatus { #[derive(Debug, Clone)] pub enum LoadError { + NotInstalled { + error_message: SharedString, + install_message: SharedString, + install_command: String, + }, Unsupported { error_message: SharedString, upgrade_message: SharedString, upgrade_command: String, }, - Exited(i32), + Exited { + status: ExitStatus, + }, Other(SharedString), } impl Display for LoadError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - LoadError::Unsupported { error_message, .. } => write!(f, "{}", error_message), - LoadError::Exited(status) => write!(f, "Server exited with status {}", status), + LoadError::NotInstalled { error_message, .. } + | LoadError::Unsupported { error_message, .. } => { + write!(f, "{error_message}") + } + LoadError::Exited { status } => write!(f, "Server exited with status {status}"), LoadError::Other(msg) => write!(f, "{}", msg), } } @@ -717,11 +757,9 @@ impl AcpThread { title: impl Into, connection: Rc, project: Entity, + action_log: Entity, session_id: acp::SessionId, - cx: &mut Context, ) -> Self { - let action_log = cx.new(|_| ActionLog::new(project.clone())); - Self { action_log, shared_buffers: Default::default(), @@ -732,6 +770,7 @@ impl AcpThread { send_task: None, connection, session_id, + token_usage: None, } } @@ -771,6 +810,10 @@ impl AcpThread { } } + pub fn token_usage(&self) -> Option<&TokenUsage> { + self.token_usage.as_ref() + } + pub fn has_pending_edit_tool_calls(&self) -> bool { for entry in self.entries.iter().rev() { match entry { @@ -915,6 +958,21 @@ impl AcpThread { cx.emit(AcpThreadEvent::NewEntry); } + pub fn update_title(&mut self, title: SharedString, cx: &mut Context) -> Result<()> { + self.title = title; + cx.emit(AcpThreadEvent::TitleUpdated); + Ok(()) + } + + pub fn update_token_usage(&mut self, usage: Option, cx: &mut Context) { + self.token_usage = usage; + cx.emit(AcpThreadEvent::TokenUsageUpdated); + } + + pub fn update_retry_status(&mut self, status: RetryStatus, cx: &mut Context) { + cx.emit(AcpThreadEvent::Retry(status)); + } + pub fn update_tool_call( &mut self, update: impl Into, @@ -1006,6 +1064,22 @@ impl AcpThread { }) } + pub fn tool_call(&mut self, id: &acp::ToolCallId) -> Option<(usize, &ToolCall)> { + self.entries + .iter() + .enumerate() + .rev() + .find_map(|(index, tool_call)| { + if let AgentThreadEntry::ToolCall(tool_call) = tool_call + && &tool_call.id == id + { + Some((index, tool_call)) + } else { + None + } + }) + } + pub fn resolve_locations(&mut self, id: acp::ToolCallId, cx: &mut Context) { let project = self.project.clone(); let Some((_, tool_call)) = self.tool_call_mut(&id) else { @@ -1199,17 +1273,21 @@ impl AcpThread { } else { None }; - self.push_entry( - AgentThreadEntry::UserMessage(UserMessage { - id: message_id.clone(), - content: block, - chunks: message, - checkpoint: None, - }), - cx, - ); self.run_turn(cx, async move |this, cx| { + this.update(cx, |this, cx| { + this.push_entry( + AgentThreadEntry::UserMessage(UserMessage { + id: message_id.clone(), + content: block, + chunks: message, + checkpoint: None, + }), + cx, + ); + }) + .ok(); + let old_checkpoint = git_store .update(cx, |git, cx| git.checkpoint(cx))? .await @@ -1262,6 +1340,8 @@ impl AcpThread { .await?; this.update(cx, |this, cx| { + this.project + .update(cx, |project, cx| project.set_agent_location(None, cx)); match response { Ok(Err(e)) => { this.send_task.take(); @@ -1411,7 +1491,7 @@ impl AcpThread { fn user_message(&self, id: &UserMessageId) -> Option<&UserMessage> { self.entries.iter().find_map(|entry| { if let AgentThreadEntry::UserMessage(message) = entry { - if message.id.as_ref() == Some(&id) { + if message.id.as_ref() == Some(id) { Some(message) } else { None @@ -1425,7 +1505,7 @@ impl AcpThread { fn user_message_mut(&mut self, id: &UserMessageId) -> Option<(usize, &mut UserMessage)> { self.entries.iter_mut().enumerate().find_map(|(ix, entry)| { if let AgentThreadEntry::UserMessage(message) = entry { - if message.id.as_ref() == Some(&id) { + if message.id.as_ref() == Some(id) { Some((ix, message)) } else { None @@ -1550,30 +1630,59 @@ impl AcpThread { .collect::>() }) .await; - cx.update(|cx| { - project.update(cx, |project, cx| { - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position: edits - .last() - .map(|(range, _)| range.end) - .unwrap_or(Anchor::MIN), - }), - cx, - ); - }); + project.update(cx, |project, cx| { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: edits + .last() + .map(|(range, _)| range.end) + .unwrap_or(Anchor::MIN), + }), + cx, + ); + })?; + + let format_on_save = cx.update(|cx| { action_log.update(cx, |action_log, cx| { action_log.buffer_read(buffer.clone(), cx); }); - buffer.update(cx, |buffer, cx| { + + let format_on_save = buffer.update(cx, |buffer, cx| { buffer.edit(edits, None, cx); + + let settings = language::language_settings::language_settings( + buffer.language().map(|l| l.name()), + buffer.file(), + cx, + ); + + settings.format_on_save != FormatOnSave::Off }); action_log.update(cx, |action_log, cx| { action_log.buffer_edited(buffer.clone(), cx); }); + format_on_save })?; + + if format_on_save { + let format_task = project.update(cx, |project, cx| { + project.format( + HashSet::from_iter([buffer.clone()]), + LspFormatTarget::Buffers, + false, + FormatTrigger::Save, + cx, + ) + })?; + format_task.await.log_err(); + + action_log.update(cx, |action_log, cx| { + action_log.buffer_edited(buffer.clone(), cx); + })?; + } + project .update(cx, |project, cx| project.save_buffer(buffer, cx))? .await @@ -1584,8 +1693,8 @@ impl AcpThread { self.entries.iter().map(|e| e.to_markdown(cx)).collect() } - pub fn emit_server_exited(&mut self, status: ExitStatus, cx: &mut Context) { - cx.emit(AcpThreadEvent::ServerExited(status)); + pub fn emit_load_error(&mut self, error: LoadError, cx: &mut Context) { + cx.emit(AcpThreadEvent::LoadError(error)); } } @@ -1636,7 +1745,7 @@ mod tests { use super::*; use anyhow::anyhow; use futures::{channel::mpsc, future::LocalBoxFuture, select}; - use gpui::{AsyncApp, TestAppContext, WeakEntity}; + use gpui::{App, AsyncApp, TestAppContext, WeakEntity}; use indoc::indoc; use project::{FakeFs, Fs}; use rand::Rng as _; @@ -2123,7 +2232,7 @@ mod tests { "} ); }); - assert_eq!(fs.files(), vec![Path::new("/test/file-0")]); + assert_eq!(fs.files(), vec![Path::new(path!("/test/file-0"))]); cx.update(|cx| thread.update(cx, |thread, cx| thread.send(vec!["ipsum".into()], cx))) .await @@ -2153,7 +2262,10 @@ mod tests { }); assert_eq!( fs.files(), - vec![Path::new("/test/file-0"), Path::new("/test/file-1")] + vec![ + Path::new(path!("/test/file-0")), + Path::new(path!("/test/file-1")) + ] ); // Checkpoint isn't stored when there are no changes. @@ -2194,7 +2306,10 @@ mod tests { }); assert_eq!( fs.files(), - vec![Path::new("/test/file-0"), Path::new("/test/file-1")] + vec![ + Path::new(path!("/test/file-0")), + Path::new(path!("/test/file-1")) + ] ); // Rewinding the conversation truncates the history and restores the checkpoint. @@ -2222,7 +2337,7 @@ mod tests { "} ); }); - assert_eq!(fs.files(), vec![Path::new("/test/file-0")]); + assert_eq!(fs.files(), vec![Path::new(path!("/test/file-0"))]); } async fn run_until_first_tool_call( @@ -2306,7 +2421,7 @@ mod tests { self: Rc, project: Entity, _cwd: &Path, - cx: &mut gpui::App, + cx: &mut App, ) -> Task>> { let session_id = acp::SessionId( rand::thread_rng() @@ -2316,8 +2431,16 @@ mod tests { .collect::() .into(), ); - let thread = - cx.new(|cx| AcpThread::new("Test", self.clone(), project, session_id.clone(), cx)); + let action_log = cx.new(|_| ActionLog::new(project.clone())); + let thread = cx.new(|_cx| { + AcpThread::new( + "Test", + self.clone(), + project, + action_log, + session_id.clone(), + ) + }); self.sessions.lock().insert(session_id, thread.downgrade()); Task::ready(Ok(thread)) } @@ -2351,7 +2474,7 @@ mod tests { fn cancel(&self, session_id: &acp::SessionId, cx: &mut App) { let sessions = self.sessions.lock(); - let thread = sessions.get(&session_id).unwrap().clone(); + let thread = sessions.get(session_id).unwrap().clone(); cx.spawn(async move |cx| { thread diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs index 7497d2309f..8cae975ce5 100644 --- a/crates/acp_thread/src/connection.rs +++ b/crates/acp_thread/src/connection.rs @@ -3,12 +3,14 @@ use agent_client_protocol::{self as acp}; use anyhow::Result; use collections::IndexMap; use gpui::{Entity, SharedString, Task}; +use language_model::LanguageModelProviderId; use project::Project; +use serde::{Deserialize, Serialize}; use std::{any::Any, error::Error, fmt, path::Path, rc::Rc, sync::Arc}; use ui::{App, IconName}; use uuid::Uuid; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct UserMessageId(Arc); impl UserMessageId { @@ -80,12 +82,34 @@ pub trait AgentSessionResume { } #[derive(Debug)] -pub struct AuthRequired; +pub struct AuthRequired { + pub description: Option, + pub provider_id: Option, +} + +impl AuthRequired { + pub fn new() -> Self { + Self { + description: None, + provider_id: None, + } + } + + pub fn with_description(mut self, description: String) -> Self { + self.description = Some(description); + self + } + + pub fn with_language_model_provider(mut self, provider_id: LanguageModelProviderId) -> Self { + self.provider_id = Some(provider_id); + self + } +} impl Error for AuthRequired {} impl fmt::Display for AuthRequired { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "AuthRequired") + write!(f, "Authentication required") } } @@ -185,8 +209,9 @@ impl AgentModelList { mod test_support { use std::sync::Arc; + use action_log::ActionLog; use collections::HashMap; - use futures::future::try_join_all; + use futures::{channel::oneshot, future::try_join_all}; use gpui::{AppContext as _, WeakEntity}; use parking_lot::Mutex; @@ -194,11 +219,16 @@ mod test_support { #[derive(Clone, Default)] pub struct StubAgentConnection { - sessions: Arc>>>, + sessions: Arc>>, permission_requests: HashMap>, next_prompt_updates: Arc>>, } + struct Session { + thread: WeakEntity, + response_tx: Option>, + } + impl StubAgentConnection { pub fn new() -> Self { Self { @@ -226,15 +256,33 @@ mod test_support { update: acp::SessionUpdate, cx: &mut App, ) { + assert!( + self.next_prompt_updates.lock().is_empty(), + "Use either send_update or set_next_prompt_updates" + ); + self.sessions .lock() .get(&session_id) .unwrap() + .thread .update(cx, |thread, cx| { - thread.handle_session_update(update.clone(), cx).unwrap(); + thread.handle_session_update(update, cx).unwrap(); }) .unwrap(); } + + pub fn end_turn(&self, session_id: acp::SessionId, stop_reason: acp::StopReason) { + self.sessions + .lock() + .get_mut(&session_id) + .unwrap() + .response_tx + .take() + .expect("No pending turn") + .send(stop_reason) + .unwrap(); + } } impl AgentConnection for StubAgentConnection { @@ -249,9 +297,23 @@ mod test_support { cx: &mut gpui::App, ) -> Task>> { let session_id = acp::SessionId(self.sessions.lock().len().to_string().into()); - let thread = - cx.new(|cx| AcpThread::new("Test", self.clone(), project, session_id.clone(), cx)); - self.sessions.lock().insert(session_id, thread.downgrade()); + let action_log = cx.new(|_| ActionLog::new(project.clone())); + let thread = cx.new(|_cx| { + AcpThread::new( + "Test", + self.clone(), + project, + action_log, + session_id.clone(), + ) + }); + self.sessions.lock().insert( + session_id, + Session { + thread: thread.downgrade(), + response_tx: None, + }, + ); Task::ready(Ok(thread)) } @@ -269,47 +331,70 @@ mod test_support { params: acp::PromptRequest, cx: &mut App, ) -> Task> { - let sessions = self.sessions.lock(); - let thread = sessions.get(¶ms.session_id).unwrap(); + let mut sessions = self.sessions.lock(); + let Session { + thread, + response_tx, + } = sessions.get_mut(¶ms.session_id).unwrap(); let mut tasks = vec![]; - for update in self.next_prompt_updates.lock().drain(..) { - let thread = thread.clone(); - let update = update.clone(); - let permission_request = if let acp::SessionUpdate::ToolCall(tool_call) = &update - && let Some(options) = self.permission_requests.get(&tool_call.id) - { - Some((tool_call.clone(), options.clone())) - } else { - None - }; - let task = cx.spawn(async move |cx| { - if let Some((tool_call, options)) = permission_request { - let permission = thread.update(cx, |thread, cx| { - thread.request_tool_call_authorization( - tool_call.clone().into(), - options.clone(), - cx, - ) - })?; - permission?.await?; - } - thread.update(cx, |thread, cx| { - thread.handle_session_update(update.clone(), cx).unwrap(); - })?; - anyhow::Ok(()) - }); - tasks.push(task); - } - cx.spawn(async move |_| { - try_join_all(tasks).await?; - Ok(acp::PromptResponse { - stop_reason: acp::StopReason::EndTurn, + if self.next_prompt_updates.lock().is_empty() { + let (tx, rx) = oneshot::channel(); + response_tx.replace(tx); + cx.spawn(async move |_| { + let stop_reason = rx.await?; + Ok(acp::PromptResponse { stop_reason }) }) - }) + } else { + for update in self.next_prompt_updates.lock().drain(..) { + let thread = thread.clone(); + let update = update.clone(); + let permission_request = if let acp::SessionUpdate::ToolCall(tool_call) = + &update + && let Some(options) = self.permission_requests.get(&tool_call.id) + { + Some((tool_call.clone(), options.clone())) + } else { + None + }; + let task = cx.spawn(async move |cx| { + if let Some((tool_call, options)) = permission_request { + let permission = thread.update(cx, |thread, cx| { + thread.request_tool_call_authorization( + tool_call.clone().into(), + options.clone(), + cx, + ) + })?; + permission?.await?; + } + thread.update(cx, |thread, cx| { + thread.handle_session_update(update.clone(), cx).unwrap(); + })?; + anyhow::Ok(()) + }); + tasks.push(task); + } + + cx.spawn(async move |_| { + try_join_all(tasks).await?; + Ok(acp::PromptResponse { + stop_reason: acp::StopReason::EndTurn, + }) + }) + } } - fn cancel(&self, _session_id: &acp::SessionId, _cx: &mut App) { - unimplemented!() + fn cancel(&self, session_id: &acp::SessionId, _cx: &mut App) { + if let Some(end_turn_tx) = self + .sessions + .lock() + .get_mut(session_id) + .unwrap() + .response_tx + .take() + { + end_turn_tx.send(acp::StopReason::Canceled).unwrap(); + } } fn session_editor( diff --git a/crates/acp_thread/src/diff.rs b/crates/acp_thread/src/diff.rs index a2c2d6c322..4b779931c5 100644 --- a/crates/acp_thread/src/diff.rs +++ b/crates/acp_thread/src/diff.rs @@ -1,4 +1,3 @@ -use agent_client_protocol as acp; use anyhow::Result; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; use editor::{MultiBuffer, PathKey}; @@ -21,17 +20,13 @@ pub enum Diff { } impl Diff { - pub fn from_acp( - diff: acp::Diff, + pub fn finalized( + path: PathBuf, + old_text: Option, + new_text: String, language_registry: Arc, cx: &mut Context, ) -> Self { - let acp::Diff { - path, - old_text, - new_text, - } = diff; - let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly)); let new_buffer = cx.new(|cx| Buffer::local(new_text, cx)); @@ -71,8 +66,8 @@ impl Diff { let hunk_ranges = { let buffer = new_buffer.read(cx); let diff = buffer_diff.read(cx); - diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, cx) - .map(|diff_hunk| diff_hunk.buffer_range.to_point(&buffer)) + diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx) + .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer)) .collect::>() }; @@ -306,13 +301,13 @@ impl PendingDiff { let buffer = self.buffer.read(cx); let diff = self.diff.read(cx); let mut ranges = diff - .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, cx) - .map(|diff_hunk| diff_hunk.buffer_range.to_point(&buffer)) + .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx) + .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer)) .collect::>(); ranges.extend( self.revealed_ranges .iter() - .map(|range| range.to_point(&buffer)), + .map(|range| range.to_point(buffer)), ); ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end))); diff --git a/crates/acp_thread/src/mention.rs b/crates/acp_thread/src/mention.rs index b9b021c4ca..a1e713cffa 100644 --- a/crates/acp_thread/src/mention.rs +++ b/crates/acp_thread/src/mention.rs @@ -1,7 +1,8 @@ -use agent::ThreadId; +use agent_client_protocol as acp; use anyhow::{Context as _, Result, bail}; use file_icons::FileIcons; use prompt_store::{PromptId, UserPromptId}; +use serde::{Deserialize, Serialize}; use std::{ fmt, ops::Range, @@ -11,11 +12,13 @@ use std::{ use ui::{App, IconName, SharedString}; use url::Url; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] pub enum MentionUri { File { abs_path: PathBuf, - is_directory: bool, + }, + Directory { + abs_path: PathBuf, }, Symbol { path: PathBuf, @@ -23,7 +26,7 @@ pub enum MentionUri { line_range: Range, }, Thread { - id: ThreadId, + id: acp::SessionId, name: String, }, TextThread { @@ -49,6 +52,7 @@ impl MentionUri { let path = url.path(); match url.scheme() { "file" => { + let path = url.to_file_path().ok().context("Extracting file path")?; if let Some(fragment) = url.fragment() { let range = fragment .strip_prefix("L") @@ -69,31 +73,23 @@ impl MentionUri { if let Some(name) = single_query_param(&url, "symbol")? { Ok(Self::Symbol { name, - path: path.into(), + path, line_range, }) } else { - Ok(Self::Selection { - path: path.into(), - line_range, - }) + Ok(Self::Selection { path, line_range }) } + } else if input.ends_with("/") { + Ok(Self::Directory { abs_path: path }) } else { - let file_path = - PathBuf::from(format!("{}{}", url.host_str().unwrap_or(""), path)); - let is_directory = input.ends_with("/"); - - Ok(Self::File { - abs_path: file_path, - is_directory, - }) + Ok(Self::File { abs_path: path }) } } "zed" => { if let Some(thread_id) = path.strip_prefix("/agent/thread/") { let name = single_query_param(&url, "name")?.context("Missing thread name")?; Ok(Self::Thread { - id: thread_id.into(), + id: acp::SessionId(thread_id.into()), name, }) } else if let Some(path) = path.strip_prefix("/agent/text-thread/") { @@ -120,7 +116,7 @@ impl MentionUri { pub fn name(&self) -> String { match self { - MentionUri::File { abs_path, .. } => abs_path + MentionUri::File { abs_path, .. } | MentionUri::Directory { abs_path, .. } => abs_path .file_name() .unwrap_or_default() .to_string_lossy() @@ -138,18 +134,11 @@ impl MentionUri { pub fn icon_path(&self, cx: &mut App) -> SharedString { match self { - MentionUri::File { - abs_path, - is_directory, - } => { - if *is_directory { - FileIcons::get_folder_icon(false, cx) - .unwrap_or_else(|| IconName::Folder.path().into()) - } else { - FileIcons::get_icon(&abs_path, cx) - .unwrap_or_else(|| IconName::File.path().into()) - } + MentionUri::File { abs_path } => { + FileIcons::get_icon(abs_path, cx).unwrap_or_else(|| IconName::File.path().into()) } + MentionUri::Directory { .. } => FileIcons::get_folder_icon(false, cx) + .unwrap_or_else(|| IconName::Folder.path().into()), MentionUri::Symbol { .. } => IconName::Code.path().into(), MentionUri::Thread { .. } => IconName::Thread.path().into(), MentionUri::TextThread { .. } => IconName::Thread.path().into(), @@ -165,25 +154,18 @@ impl MentionUri { pub fn to_uri(&self) -> Url { match self { - MentionUri::File { - abs_path, - is_directory, - } => { - let mut url = Url::parse("file:///").unwrap(); - let mut path = abs_path.to_string_lossy().to_string(); - if *is_directory && !path.ends_with("/") { - path.push_str("/"); - } - url.set_path(&path); - url + MentionUri::File { abs_path } => { + Url::from_file_path(abs_path).expect("mention path should be absolute") + } + MentionUri::Directory { abs_path } => { + Url::from_directory_path(abs_path).expect("mention path should be absolute") } MentionUri::Symbol { path, name, line_range, } => { - let mut url = Url::parse("file:///").unwrap(); - url.set_path(&path.to_string_lossy()); + let mut url = Url::from_file_path(path).expect("mention path should be absolute"); url.query_pairs_mut().append_pair("symbol", name); url.set_fragment(Some(&format!( "L{}:{}", @@ -193,8 +175,7 @@ impl MentionUri { url } MentionUri::Selection { path, line_range } => { - let mut url = Url::parse("file:///").unwrap(); - url.set_path(&path.to_string_lossy()); + let mut url = Url::from_file_path(path).expect("mention path should be absolute"); url.set_fragment(Some(&format!( "L{}:{}", line_range.start + 1, @@ -267,19 +248,17 @@ pub fn selection_name(path: &Path, line_range: &Range) -> String { #[cfg(test)] mod tests { + use util::{path, uri}; + use super::*; #[test] fn test_parse_file_uri() { - let file_uri = "file:///path/to/file.rs"; + let file_uri = uri!("file:///path/to/file.rs"); let parsed = MentionUri::parse(file_uri).unwrap(); match &parsed { - MentionUri::File { - abs_path, - is_directory, - } => { - assert_eq!(abs_path.to_str().unwrap(), "/path/to/file.rs"); - assert!(!is_directory); + MentionUri::File { abs_path } => { + assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/file.rs")); } _ => panic!("Expected File variant"), } @@ -288,42 +267,38 @@ mod tests { #[test] fn test_parse_directory_uri() { - let file_uri = "file:///path/to/dir/"; + let file_uri = uri!("file:///path/to/dir/"); let parsed = MentionUri::parse(file_uri).unwrap(); match &parsed { - MentionUri::File { - abs_path, - is_directory, - } => { - assert_eq!(abs_path.to_str().unwrap(), "/path/to/dir/"); - assert!(is_directory); + MentionUri::Directory { abs_path } => { + assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/dir/")); } - _ => panic!("Expected File variant"), + _ => panic!("Expected Directory variant"), } assert_eq!(parsed.to_uri().to_string(), file_uri); } #[test] fn test_to_directory_uri_with_slash() { - let uri = MentionUri::File { - abs_path: PathBuf::from("/path/to/dir/"), - is_directory: true, + let uri = MentionUri::Directory { + abs_path: PathBuf::from(path!("/path/to/dir/")), }; - assert_eq!(uri.to_uri().to_string(), "file:///path/to/dir/"); + let expected = uri!("file:///path/to/dir/"); + assert_eq!(uri.to_uri().to_string(), expected); } #[test] fn test_to_directory_uri_without_slash() { - let uri = MentionUri::File { - abs_path: PathBuf::from("/path/to/dir"), - is_directory: true, + let uri = MentionUri::Directory { + abs_path: PathBuf::from(path!("/path/to/dir")), }; - assert_eq!(uri.to_uri().to_string(), "file:///path/to/dir/"); + let expected = uri!("file:///path/to/dir/"); + assert_eq!(uri.to_uri().to_string(), expected); } #[test] fn test_parse_symbol_uri() { - let symbol_uri = "file:///path/to/file.rs?symbol=MySymbol#L10:20"; + let symbol_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20"); let parsed = MentionUri::parse(symbol_uri).unwrap(); match &parsed { MentionUri::Symbol { @@ -331,7 +306,7 @@ mod tests { name, line_range, } => { - assert_eq!(path.to_str().unwrap(), "/path/to/file.rs"); + assert_eq!(path.to_str().unwrap(), path!("/path/to/file.rs")); assert_eq!(name, "MySymbol"); assert_eq!(line_range.start, 9); assert_eq!(line_range.end, 19); @@ -343,11 +318,11 @@ mod tests { #[test] fn test_parse_selection_uri() { - let selection_uri = "file:///path/to/file.rs#L5:15"; + let selection_uri = uri!("file:///path/to/file.rs#L5:15"); let parsed = MentionUri::parse(selection_uri).unwrap(); match &parsed { MentionUri::Selection { path, line_range } => { - assert_eq!(path.to_str().unwrap(), "/path/to/file.rs"); + assert_eq!(path.to_str().unwrap(), path!("/path/to/file.rs")); assert_eq!(line_range.start, 4); assert_eq!(line_range.end, 14); } @@ -429,32 +404,35 @@ mod tests { #[test] fn test_invalid_line_range_format() { // Missing L prefix - assert!(MentionUri::parse("file:///path/to/file.rs#10:20").is_err()); + assert!(MentionUri::parse(uri!("file:///path/to/file.rs#10:20")).is_err()); // Missing colon separator - assert!(MentionUri::parse("file:///path/to/file.rs#L1020").is_err()); + assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L1020")).is_err()); // Invalid numbers - assert!(MentionUri::parse("file:///path/to/file.rs#L10:abc").is_err()); - assert!(MentionUri::parse("file:///path/to/file.rs#Labc:20").is_err()); + assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L10:abc")).is_err()); + assert!(MentionUri::parse(uri!("file:///path/to/file.rs#Labc:20")).is_err()); } #[test] fn test_invalid_query_parameters() { // Invalid query parameter name - assert!(MentionUri::parse("file:///path/to/file.rs#L10:20?invalid=test").is_err()); + assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L10:20?invalid=test")).is_err()); // Too many query parameters assert!( - MentionUri::parse("file:///path/to/file.rs#L10:20?symbol=test&another=param").is_err() + MentionUri::parse(uri!( + "file:///path/to/file.rs#L10:20?symbol=test&another=param" + )) + .is_err() ); } #[test] fn test_zero_based_line_numbers() { // Test that 0-based line numbers are rejected (should be 1-based) - assert!(MentionUri::parse("file:///path/to/file.rs#L0:10").is_err()); - assert!(MentionUri::parse("file:///path/to/file.rs#L1:0").is_err()); - assert!(MentionUri::parse("file:///path/to/file.rs#L0:0").is_err()); + assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L0:10")).is_err()); + assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L1:0")).is_err()); + assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L0:0")).is_err()); } } diff --git a/crates/action_log/src/action_log.rs b/crates/action_log/src/action_log.rs index c4eaffc228..1c3cad386d 100644 --- a/crates/action_log/src/action_log.rs +++ b/crates/action_log/src/action_log.rs @@ -116,7 +116,7 @@ impl ActionLog { } else if buffer .read(cx) .file() - .map_or(false, |file| file.disk_state().exists()) + .is_some_and(|file| file.disk_state().exists()) { TrackedBufferStatus::Created { existing_file_content: Some(buffer.read(cx).as_rope().clone()), @@ -215,7 +215,7 @@ impl ActionLog { if buffer .read(cx) .file() - .map_or(false, |file| file.disk_state() == DiskState::Deleted) + .is_some_and(|file| file.disk_state() == DiskState::Deleted) { // If the buffer had been edited by a tool, but it got // deleted externally, we want to stop tracking it. @@ -227,7 +227,7 @@ impl ActionLog { if buffer .read(cx) .file() - .map_or(false, |file| file.disk_state() != DiskState::Deleted) + .is_some_and(|file| file.disk_state() != DiskState::Deleted) { // If the buffer had been deleted by a tool, but it got // resurrected externally, we want to clear the edits we @@ -264,15 +264,14 @@ impl ActionLog { if let Some((git_diff, (buffer_repo, _))) = git_diff.as_ref().zip(buffer_repo) { cx.update(|cx| { let mut old_head = buffer_repo.read(cx).head_commit.clone(); - Some(cx.subscribe(git_diff, move |_, event, cx| match event { - buffer_diff::BufferDiffEvent::DiffChanged { .. } => { + Some(cx.subscribe(git_diff, move |_, event, cx| { + if let buffer_diff::BufferDiffEvent::DiffChanged { .. } = event { let new_head = buffer_repo.read(cx).head_commit.clone(); if new_head != old_head { old_head = new_head; git_diff_updates_tx.send(()).ok(); } } - _ => {} })) })? } else { @@ -290,7 +289,7 @@ impl ActionLog { } _ = git_diff_updates_rx.changed().fuse() => { if let Some(git_diff) = git_diff.as_ref() { - Self::keep_committed_edits(&this, &buffer, &git_diff, cx).await?; + Self::keep_committed_edits(&this, &buffer, git_diff, cx).await?; } } } @@ -498,7 +497,7 @@ impl ActionLog { new: new_range, }, &new_diff_base, - &buffer_snapshot.as_rope(), + buffer_snapshot.as_rope(), )); } unreviewed_edits @@ -614,10 +613,10 @@ impl ActionLog { false } }); - if tracked_buffer.unreviewed_edits.is_empty() { - if let TrackedBufferStatus::Created { .. } = &mut tracked_buffer.status { - tracked_buffer.status = TrackedBufferStatus::Modified; - } + if tracked_buffer.unreviewed_edits.is_empty() + && let TrackedBufferStatus::Created { .. } = &mut tracked_buffer.status + { + tracked_buffer.status = TrackedBufferStatus::Modified; } tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); } @@ -811,7 +810,7 @@ impl ActionLog { tracked.version != buffer.version && buffer .file() - .map_or(false, |file| file.disk_state() != DiskState::Deleted) + .is_some_and(|file| file.disk_state() != DiskState::Deleted) }) .map(|(buffer, _)| buffer) } @@ -847,7 +846,7 @@ fn apply_non_conflicting_edits( conflict = true; if new_edits .peek() - .map_or(false, |next_edit| next_edit.old.overlaps(&old_edit.new)) + .is_some_and(|next_edit| next_edit.old.overlaps(&old_edit.new)) { new_edit = new_edits.next().unwrap(); } else { @@ -964,7 +963,7 @@ impl TrackedBuffer { fn has_edits(&self, cx: &App) -> bool { self.diff .read(cx) - .hunks(&self.buffer.read(cx), cx) + .hunks(self.buffer.read(cx), cx) .next() .is_some() } @@ -2268,7 +2267,7 @@ mod tests { log::info!("quiescing..."); cx.run_until_parked(); action_log.update(cx, |log, cx| { - let tracked_buffer = log.tracked_buffers.get(&buffer).unwrap(); + let tracked_buffer = log.tracked_buffers.get(buffer).unwrap(); let mut old_text = tracked_buffer.diff_base.clone(); let new_text = buffer.read(cx).as_rope(); for edit in tracked_buffer.unreviewed_edits.edits() { diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 7c562aaba4..324480f5b4 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -103,26 +103,21 @@ impl ActivityIndicator { cx.subscribe_in( &workspace_handle, window, - |activity_indicator, _, event, window, cx| match event { - workspace::Event::ClearActivityIndicator { .. } => { - if activity_indicator.statuses.pop().is_some() { - activity_indicator.dismiss_error_message( - &DismissErrorMessage, - window, - cx, - ); - cx.notify(); - } + |activity_indicator, _, event, window, cx| { + if let workspace::Event::ClearActivityIndicator { .. } = event + && activity_indicator.statuses.pop().is_some() + { + activity_indicator.dismiss_error_message(&DismissErrorMessage, window, cx); + cx.notify(); } - _ => {} }, ) .detach(); cx.subscribe( &project.read(cx).lsp_store(), - |activity_indicator, _, event, cx| match event { - LspStoreEvent::LanguageServerUpdate { name, message, .. } => { + |activity_indicator, _, event, cx| { + if let LspStoreEvent::LanguageServerUpdate { name, message, .. } = event { if let proto::update_language_server::Variant::StatusUpdate(status_update) = message { @@ -191,7 +186,6 @@ impl ActivityIndicator { } cx.notify() } - _ => {} }, ) .detach(); @@ -206,9 +200,10 @@ impl ActivityIndicator { cx.subscribe( &project.read(cx).git_store().clone(), - |_, _, event: &GitStoreEvent, cx| match event { - project::git_store::GitStoreEvent::JobsUpdated => cx.notify(), - _ => {} + |_, _, event: &GitStoreEvent, cx| { + if let project::git_store::GitStoreEvent::JobsUpdated = event { + cx.notify() + } }, ) .detach(); @@ -458,26 +453,24 @@ impl ActivityIndicator { .map(|r| r.read(cx)) .and_then(Repository::current_job); // Show any long-running git command - if let Some(job_info) = current_job { - if Instant::now() - job_info.start >= GIT_OPERATION_DELAY { - return Some(Content { - icon: Some( - Icon::new(IconName::ArrowCircle) - .size(IconSize::Small) - .with_animation( - "arrow-circle", - Animation::new(Duration::from_secs(2)).repeat(), - |icon, delta| { - icon.transform(Transformation::rotate(percentage(delta))) - }, - ) - .into_any_element(), - ), - message: job_info.message.into(), - on_click: None, - tooltip_message: None, - }); - } + if let Some(job_info) = current_job + && Instant::now() - job_info.start >= GIT_OPERATION_DELAY + { + return Some(Content { + icon: Some( + Icon::new(IconName::ArrowCircle) + .size(IconSize::Small) + .with_animation( + "arrow-circle", + Animation::new(Duration::from_secs(2)).repeat(), + |icon, delta| icon.transform(Transformation::rotate(percentage(delta))), + ) + .into_any_element(), + ), + message: job_info.message.into(), + on_click: None, + tooltip_message: None, + }); } // Show any language server installation info. @@ -702,7 +695,7 @@ impl ActivityIndicator { on_click: Some(Arc::new(|this, window, cx| { this.dismiss_error_message(&DismissErrorMessage, window, cx) })), - tooltip_message: Some(Self::version_tooltip_message(&version)), + tooltip_message: Some(Self::version_tooltip_message(version)), }), AutoUpdateStatus::Installing { version } => Some(Content { icon: Some( @@ -714,13 +707,13 @@ impl ActivityIndicator { on_click: Some(Arc::new(|this, window, cx| { this.dismiss_error_message(&DismissErrorMessage, window, cx) })), - tooltip_message: Some(Self::version_tooltip_message(&version)), + tooltip_message: Some(Self::version_tooltip_message(version)), }), AutoUpdateStatus::Updated { version } => Some(Content { icon: None, message: "Click to restart and update Zed".to_string(), on_click: Some(Arc::new(move |_, _, cx| workspace::reload(cx))), - tooltip_message: Some(Self::version_tooltip_message(&version)), + tooltip_message: Some(Self::version_tooltip_message(version)), }), AutoUpdateStatus::Errored => Some(Content { icon: Some( @@ -740,21 +733,20 @@ impl ActivityIndicator { if let Some(extension_store) = ExtensionStore::try_global(cx).map(|extension_store| extension_store.read(cx)) + && let Some(extension_id) = extension_store.outstanding_operations().keys().next() { - if let Some(extension_id) = extension_store.outstanding_operations().keys().next() { - return Some(Content { - icon: Some( - Icon::new(IconName::Download) - .size(IconSize::Small) - .into_any_element(), - ), - message: format!("Updating {extension_id} extension…"), - on_click: Some(Arc::new(|this, window, cx| { - this.dismiss_error_message(&DismissErrorMessage, window, cx) - })), - tooltip_message: None, - }); - } + return Some(Content { + icon: Some( + Icon::new(IconName::Download) + .size(IconSize::Small) + .into_any_element(), + ), + message: format!("Updating {extension_id} extension…"), + on_click: Some(Arc::new(|this, window, cx| { + this.dismiss_error_message(&DismissErrorMessage, window, cx) + })), + tooltip_message: None, + }); } None diff --git a/crates/agent/Cargo.toml b/crates/agent/Cargo.toml index 53ad2f4967..391abb38fe 100644 --- a/crates/agent/Cargo.toml +++ b/crates/agent/Cargo.toml @@ -31,7 +31,6 @@ collections.workspace = true component.workspace = true context_server.workspace = true convert_case.workspace = true -feature_flags.workspace = true fs.workspace = true futures.workspace = true git.workspace = true diff --git a/crates/agent/src/agent_profile.rs b/crates/agent/src/agent_profile.rs index 38e697dd9b..1636508df6 100644 --- a/crates/agent/src/agent_profile.rs +++ b/crates/agent/src/agent_profile.rs @@ -90,7 +90,7 @@ impl AgentProfile { return false; }; - return Self::is_enabled(settings, source, tool_name); + Self::is_enabled(settings, source, tool_name) } fn is_enabled(settings: &AgentProfileSettings, source: ToolSource, name: String) -> bool { diff --git a/crates/agent/src/context.rs b/crates/agent/src/context.rs index 8cdb87ef8d..9bb8fc0eae 100644 --- a/crates/agent/src/context.rs +++ b/crates/agent/src/context.rs @@ -201,24 +201,24 @@ impl FileContextHandle { parse_status.changed().await.log_err(); } - if let Ok(snapshot) = buffer.read_with(cx, |buffer, _| buffer.snapshot()) { - if let Some(outline) = snapshot.outline(None) { - let items = outline - .items - .into_iter() - .map(|item| item.to_point(&snapshot)); + if let Ok(snapshot) = buffer.read_with(cx, |buffer, _| buffer.snapshot()) + && let Some(outline) = snapshot.outline(None) + { + let items = outline + .items + .into_iter() + .map(|item| item.to_point(&snapshot)); - if let Ok(outline_text) = - outline::render_outline(items, None, 0, usize::MAX).await - { - let context = AgentContext::File(FileContext { - handle: self, - full_path, - text: outline_text.into(), - is_outline: true, - }); - return Some((context, vec![buffer])); - } + if let Ok(outline_text) = + outline::render_outline(items, None, 0, usize::MAX).await + { + let context = AgentContext::File(FileContext { + handle: self, + full_path, + text: outline_text.into(), + is_outline: true, + }); + return Some((context, vec![buffer])); } } } diff --git a/crates/agent/src/context_store.rs b/crates/agent/src/context_store.rs index 60ba5527dc..b531852a18 100644 --- a/crates/agent/src/context_store.rs +++ b/crates/agent/src/context_store.rs @@ -338,11 +338,9 @@ impl ContextStore { image_task, context_id: self.next_context_id.post_inc(), }); - if self.has_context(&context) { - if remove_if_exists { - self.remove_context(&context, cx); - return None; - } + if self.has_context(&context) && remove_if_exists { + self.remove_context(&context, cx); + return None; } self.insert_context(context.clone(), cx); diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index 5491842185..fc91e1bb62 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -9,14 +9,16 @@ use crate::{ tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState}, }; use action_log::ActionLog; -use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, SUMMARIZE_THREAD_PROMPT}; +use agent_settings::{ + AgentProfileId, AgentSettings, CompletionMode, SUMMARIZE_THREAD_DETAILED_PROMPT, + SUMMARIZE_THREAD_PROMPT, +}; use anyhow::{Result, anyhow}; use assistant_tool::{AnyToolCard, Tool, ToolWorkingSet}; use chrono::{DateTime, Utc}; use client::{ModelRequestUsage, RequestUsage}; use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, Plan, UsageLimit}; use collections::HashMap; -use feature_flags::{self, FeatureFlagAppExt}; use futures::{FutureExt, StreamExt as _, future::Shared}; use git::repository::DiffType; use gpui::{ @@ -108,7 +110,7 @@ impl std::fmt::Display for PromptId { } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)] -pub struct MessageId(pub(crate) usize); +pub struct MessageId(pub usize); impl MessageId { fn post_inc(&mut self) -> Self { @@ -388,7 +390,6 @@ pub struct Thread { feedback: Option, retry_state: Option, message_feedback: HashMap, - last_auto_capture_at: Option, last_received_chunk_at: Option, request_callback: Option< Box])>, @@ -489,7 +490,6 @@ impl Thread { feedback: None, retry_state: None, message_feedback: HashMap::default(), - last_auto_capture_at: None, last_error_context: None, last_received_chunk_at: None, request_callback: None, @@ -614,7 +614,6 @@ impl Thread { tool_use_limit_reached: serialized.tool_use_limit_reached, feedback: None, message_feedback: HashMap::default(), - last_auto_capture_at: None, last_error_context: None, last_received_chunk_at: None, request_callback: None, @@ -1033,8 +1032,6 @@ impl Thread { }); } - self.auto_capture_telemetry(cx); - message_id } @@ -1651,15 +1648,13 @@ impl Thread { self.tool_use .request_tool_use(tool_message_id, tool_use, tool_use_metadata.clone(), cx); - let pending_tool_use = self.tool_use.insert_tool_output( + self.tool_use.insert_tool_output( tool_use_id.clone(), tool_name, tool_output, self.configured_model.as_ref(), self.completion_mode, - ); - - pending_tool_use + ) } pub fn stream_completion( @@ -1692,7 +1687,7 @@ impl Thread { self.last_received_chunk_at = Some(Instant::now()); let task = cx.spawn(async move |thread, cx| { - let stream_completion_future = model.stream_completion(request, &cx); + let stream_completion_future = model.stream_completion(request, cx); let initial_token_usage = thread.read_with(cx, |thread, _cx| thread.cumulative_token_usage); let stream_completion = async { @@ -1824,7 +1819,7 @@ impl Thread { let streamed_input = if tool_use.is_input_complete { None } else { - Some((&tool_use.input).clone()) + Some(tool_use.input.clone()) }; let ui_text = thread.tool_use.request_tool_use( @@ -1906,7 +1901,6 @@ impl Thread { cx.emit(ThreadEvent::StreamedCompletion); cx.notify(); - thread.auto_capture_telemetry(cx); Ok(()) })??; @@ -1974,11 +1968,9 @@ impl Thread { if let Some(prev_message) = thread.messages.get(ix - 1) - { - if prev_message.role == Role::Assistant { + && prev_message.role == Role::Assistant { break; } - } } } @@ -2051,7 +2043,7 @@ impl Thread { retry_scheduled = thread .handle_retryable_error_with_delay( - &completion_error, + completion_error, Some(retry_strategy), model.clone(), intent, @@ -2081,8 +2073,6 @@ impl Thread { request_callback(request, response_events); } - thread.auto_capture_telemetry(cx); - if let Ok(initial_usage) = initial_token_usage { let usage = thread.cumulative_token_usage - initial_usage; @@ -2130,7 +2120,7 @@ impl Thread { self.pending_summary = cx.spawn(async move |this, cx| { let result = async { - let mut messages = model.model.stream_completion(request, &cx).await?; + let mut messages = model.model.stream_completion(request, cx).await?; let mut new_summary = String::new(); while let Some(event) = messages.next().await { @@ -2438,12 +2428,10 @@ impl Thread { return; } - let added_user_message = include_str!("./prompts/summarize_thread_detailed_prompt.txt"); - let request = self.to_summarize_request( &model, CompletionIntent::ThreadContextSummarization, - added_user_message.into(), + SUMMARIZE_THREAD_DETAILED_PROMPT.into(), cx, ); @@ -2456,7 +2444,7 @@ impl Thread { // which result to prefer (the old task could complete after the new one, resulting in a // stale summary). self.detailed_summary_task = cx.spawn(async move |thread, cx| { - let stream = model.stream_completion_text(request, &cx); + let stream = model.stream_completion_text(request, cx); let Some(mut messages) = stream.await.log_err() else { thread .update(cx, |thread, _cx| { @@ -2485,13 +2473,13 @@ impl Thread { .ok()?; // Save thread so its summary can be reused later - if let Some(thread) = thread.upgrade() { - if let Ok(Ok(save_task)) = cx.update(|cx| { + if let Some(thread) = thread.upgrade() + && let Ok(Ok(save_task)) = cx.update(|cx| { thread_store .update(cx, |thread_store, cx| thread_store.save_thread(&thread, cx)) - }) { - save_task.await.log_err(); - } + }) + { + save_task.await.log_err(); } Some(()) @@ -2536,7 +2524,6 @@ impl Thread { model: Arc, cx: &mut Context, ) -> Vec { - self.auto_capture_telemetry(cx); let request = Arc::new(self.to_completion_request(model.clone(), CompletionIntent::ToolResults, cx)); let pending_tool_uses = self @@ -2740,13 +2727,11 @@ impl Thread { window: Option, cx: &mut Context, ) { - if self.all_tools_finished() { - if let Some(ConfiguredModel { model, .. }) = self.configured_model.as_ref() { - if !canceled { - self.send_to_model(model.clone(), CompletionIntent::ToolResults, window, cx); - } - self.auto_capture_telemetry(cx); - } + if self.all_tools_finished() + && let Some(ConfiguredModel { model, .. }) = self.configured_model.as_ref() + && !canceled + { + self.send_to_model(model.clone(), CompletionIntent::ToolResults, window, cx); } cx.emit(ThreadEvent::ToolFinished { @@ -2933,11 +2918,11 @@ impl Thread { let buffer_store = project.read(app_cx).buffer_store(); for buffer_handle in buffer_store.read(app_cx).buffers() { let buffer = buffer_handle.read(app_cx); - if buffer.is_dirty() { - if let Some(file) = buffer.file() { - let path = file.path().to_string_lossy().to_string(); - unsaved_buffers.push(path); - } + if buffer.is_dirty() + && let Some(file) = buffer.file() + { + let path = file.path().to_string_lossy().to_string(); + unsaved_buffers.push(path); } } }) @@ -3147,50 +3132,6 @@ impl Thread { &self.project } - pub fn auto_capture_telemetry(&mut self, cx: &mut Context) { - if !cx.has_flag::() { - return; - } - - let now = Instant::now(); - if let Some(last) = self.last_auto_capture_at { - if now.duration_since(last).as_secs() < 10 { - return; - } - } - - self.last_auto_capture_at = Some(now); - - let thread_id = self.id().clone(); - let github_login = self - .project - .read(cx) - .user_store() - .read(cx) - .current_user() - .map(|user| user.github_login.clone()); - let client = self.project.read(cx).client(); - let serialize_task = self.serialize(cx); - - cx.background_executor() - .spawn(async move { - if let Ok(serialized_thread) = serialize_task.await { - if let Ok(thread_data) = serde_json::to_value(serialized_thread) { - telemetry::event!( - "Agent Thread Auto-Captured", - thread_id = thread_id.to_string(), - thread_data = thread_data, - auto_capture_reason = "tracked_user", - github_login = github_login - ); - - client.telemetry().flush_events().await; - } - } - }) - .detach(); - } - pub fn cumulative_token_usage(&self) -> TokenUsage { self.cumulative_token_usage } @@ -3233,13 +3174,13 @@ impl Thread { .model .max_token_count_for_mode(self.completion_mode().into()); - if let Some(exceeded_error) = &self.exceeded_window_error { - if model.model.id() == exceeded_error.model_id { - return Some(TotalTokenUsage { - total: exceeded_error.token_count, - max, - }); - } + if let Some(exceeded_error) = &self.exceeded_window_error + && model.model.id() == exceeded_error.model_id + { + return Some(TotalTokenUsage { + total: exceeded_error.token_count, + max, + }); } let total = self @@ -4043,7 +3984,7 @@ fn main() {{ }); let fake_model = model.as_fake(); - simulate_successful_response(&fake_model, cx); + simulate_successful_response(fake_model, cx); // Should start generating summary when there are >= 2 messages thread.read_with(cx, |thread, _| { @@ -4138,7 +4079,7 @@ fn main() {{ }); let fake_model = model.as_fake(); - simulate_successful_response(&fake_model, cx); + simulate_successful_response(fake_model, cx); thread.read_with(cx, |thread, _| { // State is still Error, not Generating @@ -5420,7 +5361,7 @@ fn main() {{ }); let fake_model = model.as_fake(); - simulate_successful_response(&fake_model, cx); + simulate_successful_response(fake_model, cx); thread.read_with(cx, |thread, _| { assert!(matches!(thread.summary(), ThreadSummary::Generating)); diff --git a/crates/agent/src/thread_store.rs b/crates/agent/src/thread_store.rs index 12c94a522d..45e551dbdf 100644 --- a/crates/agent/src/thread_store.rs +++ b/crates/agent/src/thread_store.rs @@ -42,7 +42,7 @@ use std::{ use util::ResultExt as _; pub static ZED_STATELESS: std::sync::LazyLock = - std::sync::LazyLock::new(|| std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty())); + std::sync::LazyLock::new(|| std::env::var("ZED_STATELESS").is_ok_and(|v| !v.is_empty())); #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum DataType { @@ -74,7 +74,7 @@ impl Column for DataType { } } -const RULES_FILE_NAMES: [&'static str; 9] = [ +const RULES_FILE_NAMES: [&str; 9] = [ ".rules", ".cursorrules", ".windsurfrules", @@ -581,33 +581,32 @@ impl ThreadStore { return; }; - if protocol.capable(context_server::protocol::ServerCapability::Tools) { - if let Some(response) = protocol + if protocol.capable(context_server::protocol::ServerCapability::Tools) + && let Some(response) = protocol .request::(()) .await .log_err() - { - let tool_ids = tool_working_set - .update(cx, |tool_working_set, cx| { - tool_working_set.extend( - response.tools.into_iter().map(|tool| { - Arc::new(ContextServerTool::new( - context_server_store.clone(), - server.id(), - tool, - )) as Arc - }), - cx, - ) - }) - .log_err(); + { + let tool_ids = tool_working_set + .update(cx, |tool_working_set, cx| { + tool_working_set.extend( + response.tools.into_iter().map(|tool| { + Arc::new(ContextServerTool::new( + context_server_store.clone(), + server.id(), + tool, + )) as Arc + }), + cx, + ) + }) + .log_err(); - if let Some(tool_ids) = tool_ids { - this.update(cx, |this, _| { - this.context_server_tool_ids.insert(server_id, tool_ids); - }) - .log_err(); - } + if let Some(tool_ids) = tool_ids { + this.update(cx, |this, _| { + this.context_server_tool_ids.insert(server_id, tool_ids); + }) + .log_err(); } } }) @@ -697,13 +696,14 @@ impl SerializedThreadV0_1_0 { let mut messages: Vec = Vec::with_capacity(self.0.messages.len()); for message in self.0.messages { - if message.role == Role::User && !message.tool_results.is_empty() { - if let Some(last_message) = messages.last_mut() { - debug_assert!(last_message.role == Role::Assistant); + if message.role == Role::User + && !message.tool_results.is_empty() + && let Some(last_message) = messages.last_mut() + { + debug_assert!(last_message.role == Role::Assistant); - last_message.tool_results = message.tool_results; - continue; - } + last_message.tool_results = message.tool_results; + continue; } messages.push(message); @@ -893,7 +893,7 @@ impl ThreadsDatabase { let needs_migration_from_heed = mdb_path.exists(); - let connection = if *ZED_STATELESS { + let connection = if *ZED_STATELESS || cfg!(any(feature = "test-support", test)) { Connection::open_memory(Some("THREAD_FALLBACK_DB")) } else { Connection::open_file(&sqlite_path.to_string_lossy()) diff --git a/crates/agent/src/tool_use.rs b/crates/agent/src/tool_use.rs index 74dfaf9a85..962dca591f 100644 --- a/crates/agent/src/tool_use.rs +++ b/crates/agent/src/tool_use.rs @@ -112,19 +112,13 @@ impl ToolUseState { }, ); - if let Some(window) = &mut window { - if let Some(tool) = this.tools.read(cx).tool(tool_use, cx) { - if let Some(output) = tool_result.output.clone() { - if let Some(card) = tool.deserialize_card( - output, - project.clone(), - window, - cx, - ) { - this.tool_result_cards.insert(tool_use_id, card); - } - } - } + if let Some(window) = &mut window + && let Some(tool) = this.tools.read(cx).tool(tool_use, cx) + && let Some(output) = tool_result.output.clone() + && let Some(card) = + tool.deserialize_card(output, project.clone(), window, cx) + { + this.tool_result_cards.insert(tool_use_id, card); } } } @@ -281,7 +275,7 @@ impl ToolUseState { pub fn message_has_tool_results(&self, assistant_message_id: MessageId) -> bool { self.tool_uses_by_assistant_message .get(&assistant_message_id) - .map_or(false, |results| !results.is_empty()) + .is_some_and(|results| !results.is_empty()) } pub fn tool_result( diff --git a/crates/agent2/Cargo.toml b/crates/agent2/Cargo.toml index ac1840e5e5..2a39440af8 100644 --- a/crates/agent2/Cargo.toml +++ b/crates/agent2/Cargo.toml @@ -8,24 +8,31 @@ license = "GPL-3.0-or-later" [lib] path = "src/agent2.rs" +[features] +test-support = ["db/test-support"] + [lints] workspace = true [dependencies] acp_thread.workspace = true action_log.workspace = true +agent.workspace = true agent-client-protocol.workspace = true agent_servers.workspace = true agent_settings.workspace = true anyhow.workspace = true +assistant_context.workspace = true assistant_tool.workspace = true assistant_tools.workspace = true chrono.workspace = true cloud_llm_client.workspace = true collections.workspace = true context_server.workspace = true +db.workspace = true fs.workspace = true futures.workspace = true +git.workspace = true gpui.workspace = true handlebars = { workspace = true, features = ["rust-embed"] } html_to_markdown.workspace = true @@ -37,6 +44,7 @@ language_model.workspace = true language_models.workspace = true log.workspace = true open.workspace = true +parking_lot.workspace = true paths.workspace = true portable-pty.workspace = true project.workspace = true @@ -47,6 +55,7 @@ serde.workspace = true serde_json.workspace = true settings.workspace = true smol.workspace = true +sqlez.workspace = true task.workspace = true terminal.workspace = true text.workspace = true @@ -57,15 +66,20 @@ watch.workspace = true web_search.workspace = true which.workspace = true workspace-hack.workspace = true +zstd.workspace = true [dev-dependencies] +agent = { workspace = true, "features" = ["test-support"] } +assistant_context = { workspace = true, "features" = ["test-support"] } ctor.workspace = true client = { workspace = true, "features" = ["test-support"] } clock = { workspace = true, "features" = ["test-support"] } context_server = { workspace = true, "features" = ["test-support"] } +db = { workspace = true, "features" = ["test-support"] } editor = { workspace = true, "features" = ["test-support"] } env_logger.workspace = true fs = { workspace = true, "features" = ["test-support"] } +git = { workspace = true, "features" = ["test-support"] } gpui = { workspace = true, "features" = ["test-support"] } gpui_tokio.workspace = true language = { workspace = true, "features" = ["test-support"] } diff --git a/crates/agent2/src/agent.rs b/crates/agent2/src/agent.rs index af740d9901..3c605de803 100644 --- a/crates/agent2/src/agent.rs +++ b/crates/agent2/src/agent.rs @@ -1,10 +1,10 @@ +use crate::HistoryStore; use crate::{ - AgentResponseEvent, ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DeletePathTool, - DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool, ListDirectoryTool, - MovePathTool, NowTool, OpenTool, ReadFileTool, TerminalTool, ThinkingTool, Thread, - ToolCallAuthorization, UserMessageContent, WebSearchTool, templates::Templates, + ContextServerRegistry, Thread, ThreadEvent, ThreadsDatabase, ToolCallAuthorization, + UserMessageContent, templates::Templates, }; -use acp_thread::AgentModelSelector; +use acp_thread::{AcpThread, AgentModelSelector}; +use action_log::ActionLog; use agent_client_protocol as acp; use agent_settings::AgentSettings; use anyhow::{Context as _, Result, anyhow}; @@ -22,14 +22,13 @@ use prompt_store::{ }; use settings::update_settings_file; use std::any::Any; -use std::cell::RefCell; use std::collections::HashMap; use std::path::Path; use std::rc::Rc; use std::sync::Arc; use util::ResultExt; -const RULES_FILE_NAMES: [&'static str; 9] = [ +const RULES_FILE_NAMES: [&str; 9] = [ ".rules", ".cursorrules", ".windsurfrules", @@ -51,7 +50,8 @@ struct Session { thread: Entity, /// The ACP thread that handles protocol communication acp_thread: WeakEntity, - _subscription: Subscription, + pending_save: Task<()>, + _subscriptions: Vec, } pub struct LanguageModels { @@ -91,7 +91,7 @@ impl LanguageModels { for provider in &providers { for model in provider.recommended_models(cx) { recommended_models.insert(model.id()); - recommended.push(Self::map_language_model_to_info(&model, &provider)); + recommended.push(Self::map_language_model_to_info(&model, provider)); } } if !recommended.is_empty() { @@ -155,8 +155,9 @@ impl LanguageModels { pub struct NativeAgent { /// Session ID -> Session mapping sessions: HashMap, + history: Entity, /// Shared project context for all threads - project_context: Rc>, + project_context: Entity, project_context_needs_refresh: watch::Sender<()>, _maintain_project_context: Task>, context_server_registry: Entity, @@ -173,6 +174,7 @@ pub struct NativeAgent { impl NativeAgent { pub async fn new( project: Entity, + history: Entity, templates: Arc, prompt_store: Option>, fs: Arc, @@ -200,7 +202,8 @@ impl NativeAgent { watch::channel(()); Self { sessions: HashMap::new(), - project_context: Rc::new(RefCell::new(project_context)), + history, + project_context: cx.new(|_| project_context), project_context_needs_refresh: project_context_needs_refresh_tx, _maintain_project_context: cx.spawn(async move |this, cx| { Self::maintain_project_context(this, project_context_needs_refresh_rx, cx).await @@ -218,6 +221,55 @@ impl NativeAgent { }) } + fn register_session( + &mut self, + thread_handle: Entity, + cx: &mut Context, + ) -> Entity { + let connection = Rc::new(NativeAgentConnection(cx.entity())); + let registry = LanguageModelRegistry::read_global(cx); + let summarization_model = registry.thread_summary_model().map(|c| c.model); + + thread_handle.update(cx, |thread, cx| { + thread.set_summarization_model(summarization_model, cx); + thread.add_default_tools(cx) + }); + + let thread = thread_handle.read(cx); + let session_id = thread.id().clone(); + let title = thread.title(); + let project = thread.project.clone(); + let action_log = thread.action_log.clone(); + let acp_thread = cx.new(|_cx| { + acp_thread::AcpThread::new( + title, + connection, + project.clone(), + action_log.clone(), + session_id.clone(), + ) + }); + let subscriptions = vec![ + cx.observe_release(&acp_thread, |this, acp_thread, _cx| { + this.sessions.remove(acp_thread.session_id()); + }), + cx.observe(&thread_handle, move |this, thread, cx| { + this.save_thread(thread.clone(), cx) + }), + ]; + + self.sessions.insert( + session_id, + Session { + thread: thread_handle, + acp_thread: acp_thread.downgrade(), + _subscriptions: subscriptions, + pending_save: Task::ready(()), + }, + ); + acp_thread + } + pub fn models(&self) -> &LanguageModels { &self.models } @@ -233,7 +285,9 @@ impl NativeAgent { Self::build_project_context(&this.project, this.prompt_store.as_ref(), cx) })? .await; - this.update(cx, |this, _| this.project_context.replace(project_context))?; + this.update(cx, |this, cx| { + this.project_context = cx.new(|_| project_context); + })?; } Ok(()) @@ -426,21 +480,101 @@ impl NativeAgent { ) { self.models.refresh_list(cx); - let default_model = LanguageModelRegistry::read_global(cx) - .default_model() - .map(|m| m.model.clone()); + let registry = LanguageModelRegistry::read_global(cx); + let default_model = registry.default_model().map(|m| m.model.clone()); + let summarization_model = registry.thread_summary_model().map(|m| m.model.clone()); for session in self.sessions.values_mut() { session.thread.update(cx, |thread, cx| { if thread.model().is_none() && let Some(model) = default_model.clone() { - thread.set_model(model); + thread.set_model(model, cx); cx.notify(); } + thread.set_summarization_model(summarization_model.clone(), cx); }); } } + + pub fn open_thread( + &mut self, + id: acp::SessionId, + cx: &mut Context, + ) -> Task>> { + let database_future = ThreadsDatabase::connect(cx); + cx.spawn(async move |this, cx| { + let database = database_future.await.map_err(|err| anyhow!(err))?; + let db_thread = database + .load_thread(id.clone()) + .await? + .with_context(|| format!("no thread found with ID: {id:?}"))?; + + let thread = this.update(cx, |this, cx| { + let action_log = cx.new(|_cx| ActionLog::new(this.project.clone())); + cx.new(|cx| { + Thread::from_db( + id.clone(), + db_thread, + this.project.clone(), + this.project_context.clone(), + this.context_server_registry.clone(), + action_log.clone(), + this.templates.clone(), + cx, + ) + }) + })?; + let acp_thread = + this.update(cx, |this, cx| this.register_session(thread.clone(), cx))?; + let events = thread.update(cx, |thread, cx| thread.replay(cx))?; + cx.update(|cx| { + NativeAgentConnection::handle_thread_events(events, acp_thread.downgrade(), cx) + })? + .await?; + Ok(acp_thread) + }) + } + + pub fn thread_summary( + &mut self, + id: acp::SessionId, + cx: &mut Context, + ) -> Task> { + let thread = self.open_thread(id.clone(), cx); + cx.spawn(async move |this, cx| { + let acp_thread = thread.await?; + let result = this + .update(cx, |this, cx| { + this.sessions + .get(&id) + .unwrap() + .thread + .update(cx, |thread, cx| thread.summary(cx)) + })? + .await?; + drop(acp_thread); + Ok(result) + }) + } + + fn save_thread(&mut self, thread: Entity, cx: &mut Context) { + let database_future = ThreadsDatabase::connect(cx); + let (id, db_thread) = + thread.update(cx, |thread, cx| (thread.id().clone(), thread.to_db(cx))); + let Some(session) = self.sessions.get_mut(&id) else { + return; + }; + let history = self.history.clone(); + session.pending_save = cx.spawn(async move |_, cx| { + let Some(database) = database_future.await.map_err(|err| anyhow!(err)).log_err() else { + return; + }; + let db_thread = db_thread.await; + database.save_thread(id, db_thread).await.log_err(); + history.update(cx, |history, cx| history.reload(cx)).ok(); + }); + } } /// Wrapper struct that implements the AgentConnection trait @@ -461,10 +595,7 @@ impl NativeAgentConnection { session_id: acp::SessionId, cx: &mut App, f: impl 'static - + FnOnce( - Entity, - &mut App, - ) -> Result>>, + + FnOnce(Entity, &mut App) -> Result>>, ) -> Task> { let Some((thread, acp_thread)) = self.0.update(cx, |agent, _cx| { agent @@ -476,19 +607,38 @@ impl NativeAgentConnection { }; log::debug!("Found session for: {}", session_id); - let mut response_stream = match f(thread, cx) { + let response_stream = match f(thread, cx) { Ok(stream) => stream, Err(err) => return Task::ready(Err(err)), }; + Self::handle_thread_events(response_stream, acp_thread, cx) + } + + fn handle_thread_events( + mut events: mpsc::UnboundedReceiver>, + acp_thread: WeakEntity, + cx: &App, + ) -> Task> { cx.spawn(async move |cx| { // Handle response stream and forward to session.acp_thread - while let Some(result) = response_stream.next().await { + while let Some(result) = events.next().await { match result { Ok(event) => { log::trace!("Received completion event: {:?}", event); match event { - AgentResponseEvent::Text(text) => { + ThreadEvent::UserMessage(message) => { + acp_thread.update(cx, |thread, cx| { + for content in message.content { + thread.push_user_content_block( + Some(message.id.clone()), + content.into(), + cx, + ); + } + })?; + } + ThreadEvent::AgentText(text) => { acp_thread.update(cx, |thread, cx| { thread.push_assistant_content_block( acp::ContentBlock::Text(acp::TextContent { @@ -500,7 +650,7 @@ impl NativeAgentConnection { ) })?; } - AgentResponseEvent::Thinking(text) => { + ThreadEvent::AgentThinking(text) => { acp_thread.update(cx, |thread, cx| { thread.push_assistant_content_block( acp::ContentBlock::Text(acp::TextContent { @@ -512,7 +662,7 @@ impl NativeAgentConnection { ) })?; } - AgentResponseEvent::ToolCallAuthorization(ToolCallAuthorization { + ThreadEvent::ToolCallAuthorization(ToolCallAuthorization { tool_call, options, response, @@ -535,17 +685,31 @@ impl NativeAgentConnection { }) .detach(); } - AgentResponseEvent::ToolCall(tool_call) => { + ThreadEvent::ToolCall(tool_call) => { acp_thread.update(cx, |thread, cx| { thread.upsert_tool_call(tool_call, cx) })??; } - AgentResponseEvent::ToolCallUpdate(update) => { + ThreadEvent::ToolCallUpdate(update) => { acp_thread.update(cx, |thread, cx| { thread.update_tool_call(update, cx) })??; } - AgentResponseEvent::Stop(stop_reason) => { + ThreadEvent::TokenUsageUpdate(usage) => { + acp_thread.update(cx, |thread, cx| { + thread.update_token_usage(Some(usage), cx) + })?; + } + ThreadEvent::TitleUpdate(title) => { + acp_thread + .update(cx, |thread, cx| thread.update_title(title, cx))??; + } + ThreadEvent::Retry(status) => { + acp_thread.update(cx, |thread, cx| { + thread.update_retry_status(status, cx) + })?; + } + ThreadEvent::Stop(stop_reason) => { log::debug!("Assistant message complete: {:?}", stop_reason); return Ok(acp::PromptResponse { stop_reason }); } @@ -598,8 +762,8 @@ impl AgentModelSelector for NativeAgentConnection { return Task::ready(Err(anyhow!("Invalid model ID {}", model_id))); }; - thread.update(cx, |thread, _cx| { - thread.set_model(model.clone()); + thread.update(cx, |thread, cx| { + thread.set_model(model.clone(), cx); }); update_settings_file::( @@ -659,31 +823,13 @@ impl acp_thread::AgentConnection for NativeAgentConnection { cx.spawn(async move |cx| { log::debug!("Starting thread creation in async context"); - // Generate session ID - let session_id = acp::SessionId(uuid::Uuid::new_v4().to_string().into()); - log::info!("Created session with ID: {}", session_id); - - // Create AcpThread - let acp_thread = cx.update(|cx| { - cx.new(|cx| { - acp_thread::AcpThread::new( - "agent2", - self.clone(), - project.clone(), - session_id.clone(), - cx, - ) - }) - })?; - let action_log = cx.update(|cx| acp_thread.read(cx).action_log().clone())?; - + let action_log = cx.new(|_cx| ActionLog::new(project.clone()))?; // Create Thread let thread = agent.update( cx, |agent, cx: &mut gpui::Context| -> Result<_> { // Fetch default model from registry settings let registry = LanguageModelRegistry::read_global(cx); - // Log available models for debugging let available_count = registry.available_models(cx).count(); log::debug!("Total available models: {}", available_count); @@ -695,7 +841,7 @@ impl acp_thread::AgentConnection for NativeAgentConnection { }); let thread = cx.new(|cx| { - let mut thread = Thread::new( + Thread::new( project.clone(), agent.project_context.clone(), agent.context_server_registry.clone(), @@ -703,45 +849,13 @@ impl acp_thread::AgentConnection for NativeAgentConnection { agent.templates.clone(), default_model, cx, - ); - thread.add_tool(CopyPathTool::new(project.clone())); - thread.add_tool(CreateDirectoryTool::new(project.clone())); - thread.add_tool(DeletePathTool::new(project.clone(), action_log.clone())); - thread.add_tool(DiagnosticsTool::new(project.clone())); - thread.add_tool(EditFileTool::new(cx.entity())); - thread.add_tool(FetchTool::new(project.read(cx).client().http_client())); - thread.add_tool(FindPathTool::new(project.clone())); - thread.add_tool(GrepTool::new(project.clone())); - thread.add_tool(ListDirectoryTool::new(project.clone())); - thread.add_tool(MovePathTool::new(project.clone())); - thread.add_tool(NowTool); - thread.add_tool(OpenTool::new(project.clone())); - thread.add_tool(ReadFileTool::new(project.clone(), action_log)); - thread.add_tool(TerminalTool::new(project.clone(), cx)); - thread.add_tool(ThinkingTool); - thread.add_tool(WebSearchTool); // TODO: Enable this only if it's a zed model. - thread + ) }); Ok(thread) }, )??; - - // Store the session - agent.update(cx, |agent, cx| { - agent.sessions.insert( - session_id, - Session { - thread, - acp_thread: acp_thread.downgrade(), - _subscription: cx.observe_release(&acp_thread, |this, acp_thread, _cx| { - this.sessions.remove(acp_thread.session_id()); - }), - }, - ); - })?; - - Ok(acp_thread) + agent.update(cx, |agent, cx| agent.register_session(thread, cx)) }) } @@ -797,7 +911,7 @@ impl acp_thread::AgentConnection for NativeAgentConnection { log::info!("Cancelling on session: {}", session_id); self.0.update(cx, |agent, cx| { if let Some(agent) = agent.sessions.get(session_id) { - agent.thread.update(cx, |thread, _cx| thread.cancel()); + agent.thread.update(cx, |thread, cx| thread.cancel(cx)); } }); } @@ -808,10 +922,12 @@ impl acp_thread::AgentConnection for NativeAgentConnection { cx: &mut App, ) -> Option> { self.0.update(cx, |agent, _cx| { - agent - .sessions - .get(session_id) - .map(|session| Rc::new(NativeAgentSessionEditor(session.thread.clone())) as _) + agent.sessions.get(session_id).map(|session| { + Rc::new(NativeAgentSessionEditor { + thread: session.thread.clone(), + acp_thread: session.acp_thread.clone(), + }) as _ + }) }) } @@ -820,11 +936,27 @@ impl acp_thread::AgentConnection for NativeAgentConnection { } } -struct NativeAgentSessionEditor(Entity); +struct NativeAgentSessionEditor { + thread: Entity, + acp_thread: WeakEntity, +} impl acp_thread::AgentSessionEditor for NativeAgentSessionEditor { fn truncate(&self, message_id: acp_thread::UserMessageId, cx: &mut App) -> Task> { - Task::ready(self.0.update(cx, |thread, _cx| thread.truncate(message_id))) + match self.thread.update(cx, |thread, cx| { + thread.truncate(message_id.clone(), cx)?; + Ok(thread.latest_token_usage()) + }) { + Ok(usage) => { + self.acp_thread + .update(cx, |thread, cx| { + thread.update_token_usage(usage, cx); + }) + .ok(); + Task::ready(Ok(())) + } + Err(error) => Task::ready(Err(error)), + } } } @@ -863,8 +995,11 @@ mod tests { ) .await; let project = Project::test(fs.clone(), [], cx).await; + let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx)); + let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); let agent = NativeAgent::new( project.clone(), + history_store, Templates::new(), None, fs.clone(), @@ -872,8 +1007,8 @@ mod tests { ) .await .unwrap(); - agent.read_with(cx, |agent, _| { - assert_eq!(agent.project_context.borrow().worktrees, vec![]) + agent.read_with(cx, |agent, cx| { + assert_eq!(agent.project_context.read(cx).worktrees, vec![]) }); let worktree = project @@ -881,9 +1016,9 @@ mod tests { .await .unwrap(); cx.run_until_parked(); - agent.read_with(cx, |agent, _| { + agent.read_with(cx, |agent, cx| { assert_eq!( - agent.project_context.borrow().worktrees, + agent.project_context.read(cx).worktrees, vec![WorktreeContext { root_name: "a".into(), abs_path: Path::new("/a").into(), @@ -898,7 +1033,7 @@ mod tests { agent.read_with(cx, |agent, cx| { let rules_entry = worktree.read(cx).entry_for_path(".rules").unwrap(); assert_eq!( - agent.project_context.borrow().worktrees, + agent.project_context.read(cx).worktrees, vec![WorktreeContext { root_name: "a".into(), abs_path: Path::new("/a").into(), @@ -918,9 +1053,12 @@ mod tests { let fs = FakeFs::new(cx.executor()); fs.insert_tree("/", json!({ "a": {} })).await; let project = Project::test(fs.clone(), [], cx).await; + let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx)); + let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); let connection = NativeAgentConnection( NativeAgent::new( project.clone(), + history_store, Templates::new(), None, fs.clone(), @@ -971,9 +1109,13 @@ mod tests { .await; let project = Project::test(fs.clone(), [], cx).await; + let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx)); + let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); + // Create the agent and connection let agent = NativeAgent::new( project.clone(), + history_store, Templates::new(), None, fs.clone(), diff --git a/crates/agent2/src/agent2.rs b/crates/agent2/src/agent2.rs index f13cd1bd67..1fc9c1cb95 100644 --- a/crates/agent2/src/agent2.rs +++ b/crates/agent2/src/agent2.rs @@ -1,13 +1,18 @@ mod agent; +mod db; +mod history_store; mod native_agent_server; mod templates; mod thread; +mod tool_schema; mod tools; #[cfg(test)] mod tests; pub use agent::*; +pub use db::*; +pub use history_store::*; pub use native_agent_server::NativeAgentServer; pub use templates::*; pub use thread::*; diff --git a/crates/agent2/src/db.rs b/crates/agent2/src/db.rs new file mode 100644 index 0000000000..c6a6c38201 --- /dev/null +++ b/crates/agent2/src/db.rs @@ -0,0 +1,488 @@ +use crate::{AgentMessage, AgentMessageContent, UserMessage, UserMessageContent}; +use acp_thread::UserMessageId; +use agent::{thread::DetailedSummaryState, thread_store}; +use agent_client_protocol as acp; +use agent_settings::{AgentProfileId, CompletionMode}; +use anyhow::{Result, anyhow}; +use chrono::{DateTime, Utc}; +use collections::{HashMap, IndexMap}; +use futures::{FutureExt, future::Shared}; +use gpui::{BackgroundExecutor, Global, Task}; +use indoc::indoc; +use parking_lot::Mutex; +use serde::{Deserialize, Serialize}; +use sqlez::{ + bindable::{Bind, Column}, + connection::Connection, + statement::Statement, +}; +use std::sync::Arc; +use ui::{App, SharedString}; + +pub type DbMessage = crate::Message; +pub type DbSummary = DetailedSummaryState; +pub type DbLanguageModel = thread_store::SerializedLanguageModel; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DbThreadMetadata { + pub id: acp::SessionId, + #[serde(alias = "summary")] + pub title: SharedString, + pub updated_at: DateTime, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct DbThread { + pub title: SharedString, + pub messages: Vec, + pub updated_at: DateTime, + #[serde(default)] + pub detailed_summary: Option, + #[serde(default)] + pub initial_project_snapshot: Option>, + #[serde(default)] + pub cumulative_token_usage: language_model::TokenUsage, + #[serde(default)] + pub request_token_usage: HashMap, + #[serde(default)] + pub model: Option, + #[serde(default)] + pub completion_mode: Option, + #[serde(default)] + pub profile: Option, +} + +impl DbThread { + pub const VERSION: &'static str = "0.3.0"; + + pub fn from_json(json: &[u8]) -> Result { + let saved_thread_json = serde_json::from_slice::(json)?; + match saved_thread_json.get("version") { + Some(serde_json::Value::String(version)) => match version.as_str() { + Self::VERSION => Ok(serde_json::from_value(saved_thread_json)?), + _ => Self::upgrade_from_agent_1(agent::SerializedThread::from_json(json)?), + }, + _ => Self::upgrade_from_agent_1(agent::SerializedThread::from_json(json)?), + } + } + + fn upgrade_from_agent_1(thread: agent::SerializedThread) -> Result { + let mut messages = Vec::new(); + let mut request_token_usage = HashMap::default(); + + let mut last_user_message_id = None; + for (ix, msg) in thread.messages.into_iter().enumerate() { + let message = match msg.role { + language_model::Role::User => { + let mut content = Vec::new(); + + // Convert segments to content + for segment in msg.segments { + match segment { + thread_store::SerializedMessageSegment::Text { text } => { + content.push(UserMessageContent::Text(text)); + } + thread_store::SerializedMessageSegment::Thinking { text, .. } => { + // User messages don't have thinking segments, but handle gracefully + content.push(UserMessageContent::Text(text)); + } + thread_store::SerializedMessageSegment::RedactedThinking { .. } => { + // User messages don't have redacted thinking, skip. + } + } + } + + // If no content was added, add context as text if available + if content.is_empty() && !msg.context.is_empty() { + content.push(UserMessageContent::Text(msg.context)); + } + + let id = UserMessageId::new(); + last_user_message_id = Some(id.clone()); + + crate::Message::User(UserMessage { + // MessageId from old format can't be meaningfully converted, so generate a new one + id, + content, + }) + } + language_model::Role::Assistant => { + let mut content = Vec::new(); + + // Convert segments to content + for segment in msg.segments { + match segment { + thread_store::SerializedMessageSegment::Text { text } => { + content.push(AgentMessageContent::Text(text)); + } + thread_store::SerializedMessageSegment::Thinking { + text, + signature, + } => { + content.push(AgentMessageContent::Thinking { text, signature }); + } + thread_store::SerializedMessageSegment::RedactedThinking { data } => { + content.push(AgentMessageContent::RedactedThinking(data)); + } + } + } + + // Convert tool uses + let mut tool_names_by_id = HashMap::default(); + for tool_use in msg.tool_uses { + tool_names_by_id.insert(tool_use.id.clone(), tool_use.name.clone()); + content.push(AgentMessageContent::ToolUse( + language_model::LanguageModelToolUse { + id: tool_use.id, + name: tool_use.name.into(), + raw_input: serde_json::to_string(&tool_use.input) + .unwrap_or_default(), + input: tool_use.input, + is_input_complete: true, + }, + )); + } + + // Convert tool results + let mut tool_results = IndexMap::default(); + for tool_result in msg.tool_results { + let name = tool_names_by_id + .remove(&tool_result.tool_use_id) + .unwrap_or_else(|| SharedString::from("unknown")); + tool_results.insert( + tool_result.tool_use_id.clone(), + language_model::LanguageModelToolResult { + tool_use_id: tool_result.tool_use_id, + tool_name: name.into(), + is_error: tool_result.is_error, + content: tool_result.content, + output: tool_result.output, + }, + ); + } + + if let Some(last_user_message_id) = &last_user_message_id + && let Some(token_usage) = thread.request_token_usage.get(ix).copied() + { + request_token_usage.insert(last_user_message_id.clone(), token_usage); + } + + crate::Message::Agent(AgentMessage { + content, + tool_results, + }) + } + language_model::Role::System => { + // Skip system messages as they're not supported in the new format + continue; + } + }; + + messages.push(message); + } + + Ok(Self { + title: thread.summary, + messages, + updated_at: thread.updated_at, + detailed_summary: match thread.detailed_summary_state { + DetailedSummaryState::NotGenerated | DetailedSummaryState::Generating { .. } => { + None + } + DetailedSummaryState::Generated { text, .. } => Some(text), + }, + initial_project_snapshot: thread.initial_project_snapshot, + cumulative_token_usage: thread.cumulative_token_usage, + request_token_usage, + model: thread.model, + completion_mode: thread.completion_mode, + profile: thread.profile, + }) + } +} + +pub static ZED_STATELESS: std::sync::LazyLock = + std::sync::LazyLock::new(|| std::env::var("ZED_STATELESS").is_ok_and(|v| !v.is_empty())); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum DataType { + #[serde(rename = "json")] + Json, + #[serde(rename = "zstd")] + Zstd, +} + +impl Bind for DataType { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let value = match self { + DataType::Json => "json", + DataType::Zstd => "zstd", + }; + value.bind(statement, start_index) + } +} + +impl Column for DataType { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (value, next_index) = String::column(statement, start_index)?; + let data_type = match value.as_str() { + "json" => DataType::Json, + "zstd" => DataType::Zstd, + _ => anyhow::bail!("Unknown data type: {}", value), + }; + Ok((data_type, next_index)) + } +} + +pub(crate) struct ThreadsDatabase { + executor: BackgroundExecutor, + connection: Arc>, +} + +struct GlobalThreadsDatabase(Shared, Arc>>>); + +impl Global for GlobalThreadsDatabase {} + +impl ThreadsDatabase { + pub fn connect(cx: &mut App) -> Shared, Arc>>> { + if cx.has_global::() { + return cx.global::().0.clone(); + } + let executor = cx.background_executor().clone(); + let task = executor + .spawn({ + let executor = executor.clone(); + async move { + match ThreadsDatabase::new(executor) { + Ok(db) => Ok(Arc::new(db)), + Err(err) => Err(Arc::new(err)), + } + } + }) + .shared(); + + cx.set_global(GlobalThreadsDatabase(task.clone())); + task + } + + pub fn new(executor: BackgroundExecutor) -> Result { + let connection = if *ZED_STATELESS || cfg!(any(feature = "test-support", test)) { + Connection::open_memory(Some("THREAD_FALLBACK_DB")) + } else { + let threads_dir = paths::data_dir().join("threads"); + std::fs::create_dir_all(&threads_dir)?; + let sqlite_path = threads_dir.join("threads.db"); + Connection::open_file(&sqlite_path.to_string_lossy()) + }; + + connection.exec(indoc! {" + CREATE TABLE IF NOT EXISTS threads ( + id TEXT PRIMARY KEY, + summary TEXT NOT NULL, + updated_at TEXT NOT NULL, + data_type TEXT NOT NULL, + data BLOB NOT NULL + ) + "})?() + .map_err(|e| anyhow!("Failed to create threads table: {}", e))?; + + let db = Self { + executor: executor.clone(), + connection: Arc::new(Mutex::new(connection)), + }; + + Ok(db) + } + + fn save_thread_sync( + connection: &Arc>, + id: acp::SessionId, + thread: DbThread, + ) -> Result<()> { + const COMPRESSION_LEVEL: i32 = 3; + + #[derive(Serialize)] + struct SerializedThread { + #[serde(flatten)] + thread: DbThread, + version: &'static str, + } + + let title = thread.title.to_string(); + let updated_at = thread.updated_at.to_rfc3339(); + let json_data = serde_json::to_string(&SerializedThread { + thread, + version: DbThread::VERSION, + })?; + + let connection = connection.lock(); + + let compressed = zstd::encode_all(json_data.as_bytes(), COMPRESSION_LEVEL)?; + let data_type = DataType::Zstd; + let data = compressed; + + let mut insert = connection.exec_bound::<(Arc, String, String, DataType, Vec)>(indoc! {" + INSERT OR REPLACE INTO threads (id, summary, updated_at, data_type, data) VALUES (?, ?, ?, ?, ?) + "})?; + + insert((id.0.clone(), title, updated_at, data_type, data))?; + + Ok(()) + } + + pub fn list_threads(&self) -> Task>> { + let connection = self.connection.clone(); + + self.executor.spawn(async move { + let connection = connection.lock(); + + let mut select = + connection.select_bound::<(), (Arc, String, String)>(indoc! {" + SELECT id, summary, updated_at FROM threads ORDER BY updated_at DESC + "})?; + + let rows = select(())?; + let mut threads = Vec::new(); + + for (id, summary, updated_at) in rows { + threads.push(DbThreadMetadata { + id: acp::SessionId(id), + title: summary.into(), + updated_at: DateTime::parse_from_rfc3339(&updated_at)?.with_timezone(&Utc), + }); + } + + Ok(threads) + }) + } + + pub fn load_thread(&self, id: acp::SessionId) -> Task>> { + let connection = self.connection.clone(); + + self.executor.spawn(async move { + let connection = connection.lock(); + let mut select = connection.select_bound::, (DataType, Vec)>(indoc! {" + SELECT data_type, data FROM threads WHERE id = ? LIMIT 1 + "})?; + + let rows = select(id.0)?; + if let Some((data_type, data)) = rows.into_iter().next() { + let json_data = match data_type { + DataType::Zstd => { + let decompressed = zstd::decode_all(&data[..])?; + String::from_utf8(decompressed)? + } + DataType::Json => String::from_utf8(data)?, + }; + let thread = DbThread::from_json(json_data.as_bytes())?; + Ok(Some(thread)) + } else { + Ok(None) + } + }) + } + + pub fn save_thread(&self, id: acp::SessionId, thread: DbThread) -> Task> { + let connection = self.connection.clone(); + + self.executor + .spawn(async move { Self::save_thread_sync(&connection, id, thread) }) + } + + pub fn delete_thread(&self, id: acp::SessionId) -> Task> { + let connection = self.connection.clone(); + + self.executor.spawn(async move { + let connection = connection.lock(); + + let mut delete = connection.exec_bound::>(indoc! {" + DELETE FROM threads WHERE id = ? + "})?; + + delete(id.0)?; + + Ok(()) + }) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use agent::MessageSegment; + use agent::context::LoadedContext; + use client::Client; + use fs::FakeFs; + use gpui::AppContext; + use gpui::TestAppContext; + use http_client::FakeHttpClient; + use language_model::Role; + use project::Project; + use settings::SettingsStore; + + fn init_test(cx: &mut TestAppContext) { + env_logger::try_init().ok(); + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + Project::init_settings(cx); + language::init(cx); + + let http_client = FakeHttpClient::with_404_response(); + let clock = Arc::new(clock::FakeSystemClock::new()); + let client = Client::new(clock, http_client, cx); + agent::init(cx); + agent_settings::init(cx); + language_model::init(client.clone(), cx); + }); + } + + #[gpui::test] + async fn test_retrieving_old_thread(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, [], cx).await; + + // Save a thread using the old agent. + let thread_store = cx.new(|cx| agent::ThreadStore::fake(project, cx)); + let thread = thread_store.update(cx, |thread_store, cx| thread_store.create_thread(cx)); + thread.update(cx, |thread, cx| { + thread.insert_message( + Role::User, + vec![MessageSegment::Text("Hey!".into())], + LoadedContext::default(), + vec![], + false, + cx, + ); + thread.insert_message( + Role::Assistant, + vec![MessageSegment::Text("How're you doing?".into())], + LoadedContext::default(), + vec![], + false, + cx, + ) + }); + thread_store + .update(cx, |thread_store, cx| thread_store.save_thread(&thread, cx)) + .await + .unwrap(); + + // Open that same thread using the new agent. + let db = cx.update(ThreadsDatabase::connect).await.unwrap(); + let threads = db.list_threads().await.unwrap(); + assert_eq!(threads.len(), 1); + let thread = db + .load_thread(threads[0].id.clone()) + .await + .unwrap() + .unwrap(); + assert_eq!(thread.messages[0].to_markdown(), "## User\n\nHey!\n"); + assert_eq!( + thread.messages[1].to_markdown(), + "## Assistant\n\nHow're you doing?\n" + ); + } +} diff --git a/crates/agent2/src/history_store.rs b/crates/agent2/src/history_store.rs new file mode 100644 index 0000000000..7eb7da94ba --- /dev/null +++ b/crates/agent2/src/history_store.rs @@ -0,0 +1,345 @@ +use crate::{DbThreadMetadata, ThreadsDatabase}; +use acp_thread::MentionUri; +use agent_client_protocol as acp; +use anyhow::{Context as _, Result, anyhow}; +use assistant_context::{AssistantContext, SavedContextMetadata}; +use chrono::{DateTime, Utc}; +use db::kvp::KEY_VALUE_STORE; +use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*}; +use itertools::Itertools; +use paths::contexts_dir; +use serde::{Deserialize, Serialize}; +use std::{collections::VecDeque, path::Path, sync::Arc, time::Duration}; +use util::ResultExt as _; + +const MAX_RECENTLY_OPENED_ENTRIES: usize = 6; +const RECENTLY_OPENED_THREADS_KEY: &str = "recent-agent-threads"; +const SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE: Duration = Duration::from_millis(50); + +const DEFAULT_TITLE: &SharedString = &SharedString::new_static("New Thread"); + +#[derive(Clone, Debug)] +pub enum HistoryEntry { + AcpThread(DbThreadMetadata), + TextThread(SavedContextMetadata), +} + +impl HistoryEntry { + pub fn updated_at(&self) -> DateTime { + match self { + HistoryEntry::AcpThread(thread) => thread.updated_at, + HistoryEntry::TextThread(context) => context.mtime.to_utc(), + } + } + + pub fn id(&self) -> HistoryEntryId { + match self { + HistoryEntry::AcpThread(thread) => HistoryEntryId::AcpThread(thread.id.clone()), + HistoryEntry::TextThread(context) => HistoryEntryId::TextThread(context.path.clone()), + } + } + + pub fn mention_uri(&self) -> MentionUri { + match self { + HistoryEntry::AcpThread(thread) => MentionUri::Thread { + id: thread.id.clone(), + name: thread.title.to_string(), + }, + HistoryEntry::TextThread(context) => MentionUri::TextThread { + path: context.path.as_ref().to_owned(), + name: context.title.to_string(), + }, + } + } + + pub fn title(&self) -> &SharedString { + match self { + HistoryEntry::AcpThread(thread) if thread.title.is_empty() => DEFAULT_TITLE, + HistoryEntry::AcpThread(thread) => &thread.title, + HistoryEntry::TextThread(context) => &context.title, + } + } +} + +/// Generic identifier for a history entry. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum HistoryEntryId { + AcpThread(acp::SessionId), + TextThread(Arc), +} + +#[derive(Serialize, Deserialize, Debug)] +enum SerializedRecentOpen { + AcpThread(String), + TextThread(String), +} + +pub struct HistoryStore { + threads: Vec, + context_store: Entity, + recently_opened_entries: VecDeque, + _subscriptions: Vec, + _save_recently_opened_entries_task: Task<()>, +} + +impl HistoryStore { + pub fn new( + context_store: Entity, + cx: &mut Context, + ) -> Self { + let subscriptions = vec![cx.observe(&context_store, |_, _, cx| cx.notify())]; + + cx.spawn(async move |this, cx| { + let entries = Self::load_recently_opened_entries(cx).await; + this.update(cx, |this, cx| { + if let Some(entries) = entries.log_err() { + this.recently_opened_entries = entries; + } + + this.reload(cx); + }) + .ok(); + }) + .detach(); + + Self { + context_store, + recently_opened_entries: VecDeque::default(), + threads: Vec::default(), + _subscriptions: subscriptions, + _save_recently_opened_entries_task: Task::ready(()), + } + } + + pub fn delete_thread( + &mut self, + id: acp::SessionId, + cx: &mut Context, + ) -> Task> { + let database_future = ThreadsDatabase::connect(cx); + cx.spawn(async move |this, cx| { + let database = database_future.await.map_err(|err| anyhow!(err))?; + database.delete_thread(id.clone()).await?; + this.update(cx, |this, cx| this.reload(cx)) + }) + } + + pub fn delete_text_thread( + &mut self, + path: Arc, + cx: &mut Context, + ) -> Task> { + self.context_store.update(cx, |context_store, cx| { + context_store.delete_local_context(path, cx) + }) + } + + pub fn load_text_thread( + &self, + path: Arc, + cx: &mut Context, + ) -> Task>> { + self.context_store.update(cx, |context_store, cx| { + context_store.open_local_context(path, cx) + }) + } + + pub fn reload(&self, cx: &mut Context) { + let database_future = ThreadsDatabase::connect(cx); + cx.spawn(async move |this, cx| { + let threads = database_future + .await + .map_err(|err| anyhow!(err))? + .list_threads() + .await?; + + this.update(cx, |this, cx| { + if this.recently_opened_entries.len() < MAX_RECENTLY_OPENED_ENTRIES { + for thread in threads + .iter() + .take(MAX_RECENTLY_OPENED_ENTRIES - this.recently_opened_entries.len()) + .rev() + { + this.push_recently_opened_entry( + HistoryEntryId::AcpThread(thread.id.clone()), + cx, + ) + } + } + this.threads = threads; + cx.notify(); + }) + }) + .detach_and_log_err(cx); + } + + pub fn entries(&self, cx: &App) -> Vec { + let mut history_entries = Vec::new(); + + #[cfg(debug_assertions)] + if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() { + return history_entries; + } + + history_entries.extend(self.threads.iter().cloned().map(HistoryEntry::AcpThread)); + history_entries.extend( + self.context_store + .read(cx) + .unordered_contexts() + .cloned() + .map(HistoryEntry::TextThread), + ); + + history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at())); + history_entries + } + + pub fn is_empty(&self, cx: &App) -> bool { + self.threads.is_empty() + && self + .context_store + .read(cx) + .unordered_contexts() + .next() + .is_none() + } + + pub fn recently_opened_entries(&self, cx: &App) -> Vec { + #[cfg(debug_assertions)] + if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() { + return Vec::new(); + } + + let thread_entries = self.threads.iter().flat_map(|thread| { + self.recently_opened_entries + .iter() + .enumerate() + .flat_map(|(index, entry)| match entry { + HistoryEntryId::AcpThread(id) if &thread.id == id => { + Some((index, HistoryEntry::AcpThread(thread.clone()))) + } + _ => None, + }) + }); + + let context_entries = + self.context_store + .read(cx) + .unordered_contexts() + .flat_map(|context| { + self.recently_opened_entries + .iter() + .enumerate() + .flat_map(|(index, entry)| match entry { + HistoryEntryId::TextThread(path) if &context.path == path => { + Some((index, HistoryEntry::TextThread(context.clone()))) + } + _ => None, + }) + }); + + thread_entries + .chain(context_entries) + // optimization to halt iteration early + .take(self.recently_opened_entries.len()) + .sorted_unstable_by_key(|(index, _)| *index) + .map(|(_, entry)| entry) + .collect() + } + + fn save_recently_opened_entries(&mut self, cx: &mut Context) { + let serialized_entries = self + .recently_opened_entries + .iter() + .filter_map(|entry| match entry { + HistoryEntryId::TextThread(path) => path.file_name().map(|file| { + SerializedRecentOpen::TextThread(file.to_string_lossy().to_string()) + }), + HistoryEntryId::AcpThread(id) => { + Some(SerializedRecentOpen::AcpThread(id.to_string())) + } + }) + .collect::>(); + + self._save_recently_opened_entries_task = cx.spawn(async move |_, cx| { + let content = serde_json::to_string(&serialized_entries).unwrap(); + cx.background_executor() + .timer(SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE) + .await; + + if cfg!(any(feature = "test-support", test)) { + return; + } + KEY_VALUE_STORE + .write_kvp(RECENTLY_OPENED_THREADS_KEY.to_owned(), content) + .await + .log_err(); + }); + } + + fn load_recently_opened_entries(cx: &AsyncApp) -> Task>> { + cx.background_spawn(async move { + if cfg!(any(feature = "test-support", test)) { + anyhow::bail!("history store does not persist in tests"); + } + let json = KEY_VALUE_STORE + .read_kvp(RECENTLY_OPENED_THREADS_KEY)? + .unwrap_or("[]".to_string()); + let entries = serde_json::from_str::>(&json) + .context("deserializing persisted agent panel navigation history")? + .into_iter() + .take(MAX_RECENTLY_OPENED_ENTRIES) + .flat_map(|entry| match entry { + SerializedRecentOpen::AcpThread(id) => Some(HistoryEntryId::AcpThread( + acp::SessionId(id.as_str().into()), + )), + SerializedRecentOpen::TextThread(file_name) => Some( + HistoryEntryId::TextThread(contexts_dir().join(file_name).into()), + ), + }) + .collect(); + Ok(entries) + }) + } + + pub fn push_recently_opened_entry(&mut self, entry: HistoryEntryId, cx: &mut Context) { + self.recently_opened_entries + .retain(|old_entry| old_entry != &entry); + self.recently_opened_entries.push_front(entry); + self.recently_opened_entries + .truncate(MAX_RECENTLY_OPENED_ENTRIES); + self.save_recently_opened_entries(cx); + } + + pub fn remove_recently_opened_thread(&mut self, id: acp::SessionId, cx: &mut Context) { + self.recently_opened_entries.retain(|entry| match entry { + HistoryEntryId::AcpThread(thread_id) if thread_id == &id => false, + _ => true, + }); + self.save_recently_opened_entries(cx); + } + + pub fn replace_recently_opened_text_thread( + &mut self, + old_path: &Path, + new_path: &Arc, + cx: &mut Context, + ) { + for entry in &mut self.recently_opened_entries { + match entry { + HistoryEntryId::TextThread(path) if path.as_ref() == old_path => { + *entry = HistoryEntryId::TextThread(new_path.clone()); + break; + } + _ => {} + } + } + self.save_recently_opened_entries(cx); + } + + pub fn remove_recently_opened_entry(&mut self, entry: &HistoryEntryId, cx: &mut Context) { + self.recently_opened_entries + .retain(|old_entry| old_entry != entry); + self.save_recently_opened_entries(cx); + } +} diff --git a/crates/agent2/src/native_agent_server.rs b/crates/agent2/src/native_agent_server.rs index cadd88a846..74d24efb13 100644 --- a/crates/agent2/src/native_agent_server.rs +++ b/crates/agent2/src/native_agent_server.rs @@ -1,4 +1,4 @@ -use std::{path::Path, rc::Rc, sync::Arc}; +use std::{any::Any, path::Path, rc::Rc, sync::Arc}; use agent_servers::AgentServer; use anyhow::Result; @@ -7,16 +7,17 @@ use gpui::{App, Entity, Task}; use project::Project; use prompt_store::PromptStore; -use crate::{NativeAgent, NativeAgentConnection, templates::Templates}; +use crate::{HistoryStore, NativeAgent, NativeAgentConnection, templates::Templates}; #[derive(Clone)] pub struct NativeAgentServer { fs: Arc, + history: Entity, } impl NativeAgentServer { - pub fn new(fs: Arc) -> Self { - Self { fs } + pub fn new(fs: Arc, history: Entity) -> Self { + Self { fs, history } } } @@ -26,16 +27,15 @@ impl AgentServer for NativeAgentServer { } fn empty_state_headline(&self) -> &'static str { - "Native Agent" + "" } fn empty_state_message(&self) -> &'static str { - "How can I help you today?" + "" } fn logo(&self) -> ui::IconName { - // Using the ZedAssistant icon as it's the native built-in agent - ui::IconName::ZedAssistant + ui::IconName::ZedAgent } fn connect( @@ -50,6 +50,7 @@ impl AgentServer for NativeAgentServer { ); let project = project.clone(); let fs = self.fs.clone(); + let history = self.history.clone(); let prompt_store = PromptStore::global(cx); cx.spawn(async move |cx| { log::debug!("Creating templates for native agent"); @@ -57,7 +58,8 @@ impl AgentServer for NativeAgentServer { let prompt_store = prompt_store.await?; log::debug!("Creating native agent entity"); - let agent = NativeAgent::new(project, templates, Some(prompt_store), fs, cx).await?; + let agent = + NativeAgent::new(project, history, templates, Some(prompt_store), fs, cx).await?; // Create the connection wrapper let connection = NativeAgentConnection(agent); @@ -66,4 +68,8 @@ impl AgentServer for NativeAgentServer { Ok(Rc::new(connection) as Rc) }) } + + fn into_any(self: Rc) -> Rc { + self + } } diff --git a/crates/agent2/src/templates.rs b/crates/agent2/src/templates.rs index a63f0ad206..72a8f6633c 100644 --- a/crates/agent2/src/templates.rs +++ b/crates/agent2/src/templates.rs @@ -62,7 +62,7 @@ fn contains( handlebars::RenderError::new("contains: missing or invalid query parameter") })?; - if list.contains(&query) { + if list.contains(query) { out.write("true")?; } diff --git a/crates/agent2/src/tests/mod.rs b/crates/agent2/src/tests/mod.rs index e3e3050d49..55bfa6f0b5 100644 --- a/crates/agent2/src/tests/mod.rs +++ b/crates/agent2/src/tests/mod.rs @@ -6,15 +6,16 @@ use agent_settings::AgentProfileId; use anyhow::Result; use client::{Client, UserStore}; use fs::{FakeFs, Fs}; -use futures::channel::mpsc::UnboundedReceiver; +use futures::{StreamExt, channel::mpsc::UnboundedReceiver}; use gpui::{ App, AppContext, Entity, Task, TestAppContext, UpdateGlobal, http_client::FakeHttpClient, }; use indoc::indoc; use language_model::{ - LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelRegistry, - LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse, MessageContent, - Role, StopReason, fake_provider::FakeLanguageModel, + LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, + LanguageModelProviderName, LanguageModelRegistry, LanguageModelRequestMessage, + LanguageModelToolResult, LanguageModelToolUse, MessageContent, Role, StopReason, + fake_provider::FakeLanguageModel, }; use pretty_assertions::assert_eq; use project::Project; @@ -24,8 +25,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::json; use settings::SettingsStore; -use smol::stream::StreamExt; -use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc, time::Duration}; +use std::{path::Path, rc::Rc, sync::Arc, time::Duration}; use util::path; mod test_tools; @@ -101,7 +101,9 @@ async fn test_system_prompt(cx: &mut TestAppContext) { } = setup(cx, TestModel::Fake).await; let fake_model = model.as_fake(); - project_context.borrow_mut().shell = "test-shell".into(); + project_context.update(cx, |project_context, _cx| { + project_context.shell = "test-shell".into() + }); thread.update(cx, |thread, _| thread.add_tool(EchoTool)); thread .update(cx, |thread, cx| { @@ -343,7 +345,7 @@ async fn test_streaming_tool_calls(cx: &mut TestAppContext) { let mut saw_partial_tool_use = false; while let Some(event) = events.next().await { - if let Ok(AgentResponseEvent::ToolCall(tool_call)) = event { + if let Ok(ThreadEvent::ToolCall(tool_call)) = event { thread.update(cx, |thread, _cx| { // Look for a tool use in the thread's last message let message = thread.last_message().unwrap(); @@ -733,16 +735,14 @@ async fn test_send_after_tool_use_limit(cx: &mut TestAppContext) { ); } -async fn expect_tool_call( - events: &mut UnboundedReceiver>, -) -> acp::ToolCall { +async fn expect_tool_call(events: &mut UnboundedReceiver>) -> acp::ToolCall { let event = events .next() .await .expect("no tool call authorization event received") .unwrap(); match event { - AgentResponseEvent::ToolCall(tool_call) => return tool_call, + ThreadEvent::ToolCall(tool_call) => tool_call, event => { panic!("Unexpected event {event:?}"); } @@ -750,7 +750,7 @@ async fn expect_tool_call( } async fn expect_tool_call_update_fields( - events: &mut UnboundedReceiver>, + events: &mut UnboundedReceiver>, ) -> acp::ToolCallUpdate { let event = events .next() @@ -758,9 +758,7 @@ async fn expect_tool_call_update_fields( .expect("no tool call authorization event received") .unwrap(); match event { - AgentResponseEvent::ToolCallUpdate(acp_thread::ToolCallUpdate::UpdateFields(update)) => { - return update; - } + ThreadEvent::ToolCallUpdate(acp_thread::ToolCallUpdate::UpdateFields(update)) => update, event => { panic!("Unexpected event {event:?}"); } @@ -768,7 +766,7 @@ async fn expect_tool_call_update_fields( } async fn next_tool_call_authorization( - events: &mut UnboundedReceiver>, + events: &mut UnboundedReceiver>, ) -> ToolCallAuthorization { loop { let event = events @@ -776,7 +774,7 @@ async fn next_tool_call_authorization( .await .expect("no tool call authorization event received") .unwrap(); - if let AgentResponseEvent::ToolCallAuthorization(tool_call_authorization) = event { + if let ThreadEvent::ToolCallAuthorization(tool_call_authorization) = event { let permission_kinds = tool_call_authorization .options .iter() @@ -943,13 +941,13 @@ async fn test_cancellation(cx: &mut TestAppContext) { let mut echo_completed = false; while let Some(event) = events.next().await { match event.unwrap() { - AgentResponseEvent::ToolCall(tool_call) => { + ThreadEvent::ToolCall(tool_call) => { assert_eq!(tool_call.title, expected_tools.remove(0)); if tool_call.title == "Echo" { echo_id = Some(tool_call.id); } } - AgentResponseEvent::ToolCallUpdate(acp_thread::ToolCallUpdate::UpdateFields( + ThreadEvent::ToolCallUpdate(acp_thread::ToolCallUpdate::UpdateFields( acp::ToolCallUpdate { id, fields: @@ -971,13 +969,13 @@ async fn test_cancellation(cx: &mut TestAppContext) { // Cancel the current send and ensure that the event stream is closed, even // if one of the tools is still running. - thread.update(cx, |thread, _cx| thread.cancel()); + thread.update(cx, |thread, cx| thread.cancel(cx)); let events = events.collect::>().await; let last_event = events.last(); assert!( matches!( last_event, - Some(Ok(AgentResponseEvent::Stop(acp::StopReason::Canceled))) + Some(Ok(ThreadEvent::Stop(acp::StopReason::Canceled))) ), "unexpected event {last_event:?}" ); @@ -1119,7 +1117,7 @@ async fn test_refusal(cx: &mut TestAppContext) { } #[gpui::test] -async fn test_truncate(cx: &mut TestAppContext) { +async fn test_truncate_first_message(cx: &mut TestAppContext) { let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await; let fake_model = model.as_fake(); @@ -1139,9 +1137,18 @@ async fn test_truncate(cx: &mut TestAppContext) { Hello "} ); + assert_eq!(thread.latest_token_usage(), None); }); fake_model.send_last_completion_stream_text_chunk("Hey!"); + fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::UsageUpdate( + language_model::TokenUsage { + input_tokens: 32_000, + output_tokens: 16_000, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + )); cx.run_until_parked(); thread.read_with(cx, |thread, _| { assert_eq!( @@ -1156,14 +1163,22 @@ async fn test_truncate(cx: &mut TestAppContext) { Hey! "} ); + assert_eq!( + thread.latest_token_usage(), + Some(acp_thread::TokenUsage { + used_tokens: 32_000 + 16_000, + max_tokens: 1_000_000, + }) + ); }); thread - .update(cx, |thread, _cx| thread.truncate(message_id)) + .update(cx, |thread, cx| thread.truncate(message_id, cx)) .unwrap(); cx.run_until_parked(); thread.read_with(cx, |thread, _| { assert_eq!(thread.to_markdown(), ""); + assert_eq!(thread.latest_token_usage(), None); }); // Ensure we can still send a new message after truncation. @@ -1184,6 +1199,14 @@ async fn test_truncate(cx: &mut TestAppContext) { }); cx.run_until_parked(); fake_model.send_last_completion_stream_text_chunk("Ahoy!"); + fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::UsageUpdate( + language_model::TokenUsage { + input_tokens: 40_000, + output_tokens: 20_000, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + )); cx.run_until_parked(); thread.read_with(cx, |thread, _| { assert_eq!( @@ -1198,9 +1221,171 @@ async fn test_truncate(cx: &mut TestAppContext) { Ahoy! "} ); + + assert_eq!( + thread.latest_token_usage(), + Some(acp_thread::TokenUsage { + used_tokens: 40_000 + 20_000, + max_tokens: 1_000_000, + }) + ); }); } +#[gpui::test] +async fn test_truncate_second_message(cx: &mut TestAppContext) { + let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await; + let fake_model = model.as_fake(); + + thread + .update(cx, |thread, cx| { + thread.send(UserMessageId::new(), ["Message 1"], cx) + }) + .unwrap(); + cx.run_until_parked(); + fake_model.send_last_completion_stream_text_chunk("Message 1 response"); + fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::UsageUpdate( + language_model::TokenUsage { + input_tokens: 32_000, + output_tokens: 16_000, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + )); + fake_model.end_last_completion_stream(); + cx.run_until_parked(); + + let assert_first_message_state = |cx: &mut TestAppContext| { + thread.clone().read_with(cx, |thread, _| { + assert_eq!( + thread.to_markdown(), + indoc! {" + ## User + + Message 1 + + ## Assistant + + Message 1 response + "} + ); + + assert_eq!( + thread.latest_token_usage(), + Some(acp_thread::TokenUsage { + used_tokens: 32_000 + 16_000, + max_tokens: 1_000_000, + }) + ); + }); + }; + + assert_first_message_state(cx); + + let second_message_id = UserMessageId::new(); + thread + .update(cx, |thread, cx| { + thread.send(second_message_id.clone(), ["Message 2"], cx) + }) + .unwrap(); + cx.run_until_parked(); + + fake_model.send_last_completion_stream_text_chunk("Message 2 response"); + fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::UsageUpdate( + language_model::TokenUsage { + input_tokens: 40_000, + output_tokens: 20_000, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + )); + fake_model.end_last_completion_stream(); + cx.run_until_parked(); + + thread.read_with(cx, |thread, _| { + assert_eq!( + thread.to_markdown(), + indoc! {" + ## User + + Message 1 + + ## Assistant + + Message 1 response + + ## User + + Message 2 + + ## Assistant + + Message 2 response + "} + ); + + assert_eq!( + thread.latest_token_usage(), + Some(acp_thread::TokenUsage { + used_tokens: 40_000 + 20_000, + max_tokens: 1_000_000, + }) + ); + }); + + thread + .update(cx, |thread, cx| thread.truncate(second_message_id, cx)) + .unwrap(); + cx.run_until_parked(); + + assert_first_message_state(cx); +} + +#[gpui::test] +async fn test_title_generation(cx: &mut TestAppContext) { + let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await; + let fake_model = model.as_fake(); + + let summary_model = Arc::new(FakeLanguageModel::default()); + thread.update(cx, |thread, cx| { + thread.set_summarization_model(Some(summary_model.clone()), cx) + }); + + let send = thread + .update(cx, |thread, cx| { + thread.send(UserMessageId::new(), ["Hello"], cx) + }) + .unwrap(); + cx.run_until_parked(); + + fake_model.send_last_completion_stream_text_chunk("Hey!"); + fake_model.end_last_completion_stream(); + cx.run_until_parked(); + thread.read_with(cx, |thread, _| assert_eq!(thread.title(), "New Thread")); + + // Ensure the summary model has been invoked to generate a title. + summary_model.send_last_completion_stream_text_chunk("Hello "); + summary_model.send_last_completion_stream_text_chunk("world\nG"); + summary_model.send_last_completion_stream_text_chunk("oodnight Moon"); + summary_model.end_last_completion_stream(); + send.collect::>().await; + thread.read_with(cx, |thread, _| assert_eq!(thread.title(), "Hello world")); + + // Send another message, ensuring no title is generated this time. + let send = thread + .update(cx, |thread, cx| { + thread.send(UserMessageId::new(), ["Hello again"], cx) + }) + .unwrap(); + cx.run_until_parked(); + fake_model.send_last_completion_stream_text_chunk("Hey again!"); + fake_model.end_last_completion_stream(); + cx.run_until_parked(); + assert_eq!(summary_model.pending_completions(), Vec::new()); + send.collect::>().await; + thread.read_with(cx, |thread, _| assert_eq!(thread.title(), "Hello world")); +} + #[gpui::test] async fn test_agent_connection(cx: &mut TestAppContext) { cx.update(settings::init); @@ -1228,10 +1413,13 @@ async fn test_agent_connection(cx: &mut TestAppContext) { fake_fs.insert_tree(path!("/test"), json!({})).await; let project = Project::test(fake_fs.clone(), [Path::new("/test")], cx).await; let cwd = Path::new("/test"); + let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx)); + let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); // Create agent and connection let agent = NativeAgent::new( project.clone(), + history_store, templates.clone(), None, fake_fs.clone(), @@ -1433,12 +1621,168 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { ); } +#[gpui::test] +async fn test_send_no_retry_on_success(cx: &mut TestAppContext) { + let ThreadTest { thread, model, .. } = setup(cx, TestModel::Fake).await; + let fake_model = model.as_fake(); + + let mut events = thread + .update(cx, |thread, cx| { + thread.set_completion_mode(agent_settings::CompletionMode::Burn, cx); + thread.send(UserMessageId::new(), ["Hello!"], cx) + }) + .unwrap(); + cx.run_until_parked(); + + fake_model.send_last_completion_stream_text_chunk("Hey!"); + fake_model.end_last_completion_stream(); + + let mut retry_events = Vec::new(); + while let Some(Ok(event)) = events.next().await { + match event { + ThreadEvent::Retry(retry_status) => { + retry_events.push(retry_status); + } + ThreadEvent::Stop(..) => break, + _ => {} + } + } + + assert_eq!(retry_events.len(), 0); + thread.read_with(cx, |thread, _cx| { + assert_eq!( + thread.to_markdown(), + indoc! {" + ## User + + Hello! + + ## Assistant + + Hey! + "} + ) + }); +} + +#[gpui::test] +async fn test_send_retry_on_error(cx: &mut TestAppContext) { + let ThreadTest { thread, model, .. } = setup(cx, TestModel::Fake).await; + let fake_model = model.as_fake(); + + let mut events = thread + .update(cx, |thread, cx| { + thread.set_completion_mode(agent_settings::CompletionMode::Burn, cx); + thread.send(UserMessageId::new(), ["Hello!"], cx) + }) + .unwrap(); + cx.run_until_parked(); + + fake_model.send_last_completion_stream_error(LanguageModelCompletionError::ServerOverloaded { + provider: LanguageModelProviderName::new("Anthropic"), + retry_after: Some(Duration::from_secs(3)), + }); + fake_model.end_last_completion_stream(); + + cx.executor().advance_clock(Duration::from_secs(3)); + cx.run_until_parked(); + + fake_model.send_last_completion_stream_text_chunk("Hey!"); + fake_model.end_last_completion_stream(); + + let mut retry_events = Vec::new(); + while let Some(Ok(event)) = events.next().await { + match event { + ThreadEvent::Retry(retry_status) => { + retry_events.push(retry_status); + } + ThreadEvent::Stop(..) => break, + _ => {} + } + } + + assert_eq!(retry_events.len(), 1); + assert!(matches!( + retry_events[0], + acp_thread::RetryStatus { attempt: 1, .. } + )); + thread.read_with(cx, |thread, _cx| { + assert_eq!( + thread.to_markdown(), + indoc! {" + ## User + + Hello! + + ## Assistant + + Hey! + "} + ) + }); +} + +#[gpui::test] +async fn test_send_max_retries_exceeded(cx: &mut TestAppContext) { + let ThreadTest { thread, model, .. } = setup(cx, TestModel::Fake).await; + let fake_model = model.as_fake(); + + let mut events = thread + .update(cx, |thread, cx| { + thread.set_completion_mode(agent_settings::CompletionMode::Burn, cx); + thread.send(UserMessageId::new(), ["Hello!"], cx) + }) + .unwrap(); + cx.run_until_parked(); + + for _ in 0..crate::thread::MAX_RETRY_ATTEMPTS + 1 { + fake_model.send_last_completion_stream_error( + LanguageModelCompletionError::ServerOverloaded { + provider: LanguageModelProviderName::new("Anthropic"), + retry_after: Some(Duration::from_secs(3)), + }, + ); + fake_model.end_last_completion_stream(); + cx.executor().advance_clock(Duration::from_secs(3)); + cx.run_until_parked(); + } + + let mut errors = Vec::new(); + let mut retry_events = Vec::new(); + while let Some(event) = events.next().await { + match event { + Ok(ThreadEvent::Retry(retry_status)) => { + retry_events.push(retry_status); + } + Ok(ThreadEvent::Stop(..)) => break, + Err(error) => errors.push(error), + _ => {} + } + } + + assert_eq!( + retry_events.len(), + crate::thread::MAX_RETRY_ATTEMPTS as usize + ); + for i in 0..crate::thread::MAX_RETRY_ATTEMPTS as usize { + assert_eq!(retry_events[i].attempt, i + 1); + } + assert_eq!(errors.len(), 1); + let error = errors[0] + .downcast_ref::() + .unwrap(); + assert!(matches!( + error, + LanguageModelCompletionError::ServerOverloaded { .. } + )); +} + /// Filters out the stop events for asserting against in tests -fn stop_events(result_events: Vec>) -> Vec { +fn stop_events(result_events: Vec>) -> Vec { result_events .into_iter() .filter_map(|event| match event.unwrap() { - AgentResponseEvent::Stop(stop_reason) => Some(stop_reason), + ThreadEvent::Stop(stop_reason) => Some(stop_reason), _ => None, }) .collect() @@ -1447,7 +1791,7 @@ fn stop_events(result_events: Vec>) -> Vec, thread: Entity, - project_context: Rc>, + project_context: Entity, fs: Arc, } @@ -1543,7 +1887,7 @@ async fn setup(cx: &mut TestAppContext, model: TestModel) -> ThreadTest { }) .await; - let project_context = Rc::new(RefCell::new(ProjectContext::default())); + let project_context = cx.new(|_cx| ProjectContext::default()); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); let action_log = cx.new(|_| ActionLog::new(project.clone())); diff --git a/crates/agent2/src/thread.rs b/crates/agent2/src/thread.rs index 429832010b..c1778bf38b 100644 --- a/crates/agent2/src/thread.rs +++ b/crates/agent2/src/thread.rs @@ -1,57 +1,58 @@ -use crate::{ContextServerRegistry, SystemPromptTemplate, Template, Templates}; +use crate::{ + ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread, + DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool, + ListDirectoryTool, MovePathTool, NowTool, OpenTool, ReadFileTool, SystemPromptTemplate, + Template, Templates, TerminalTool, ThinkingTool, WebSearchTool, +}; use acp_thread::{MentionUri, UserMessageId}; use action_log::ActionLog; +use agent::thread::{GitState, ProjectSnapshot, WorktreeSnapshot}; use agent_client_protocol as acp; -use agent_settings::{AgentProfileId, AgentSettings, CompletionMode}; +use agent_settings::{ + AgentProfileId, AgentSettings, CompletionMode, SUMMARIZE_THREAD_DETAILED_PROMPT, + SUMMARIZE_THREAD_PROMPT, +}; use anyhow::{Context as _, Result, anyhow}; use assistant_tool::adapt_schema_to_format; +use chrono::{DateTime, Utc}; use cloud_llm_client::{CompletionIntent, CompletionRequestStatus}; -use collections::IndexMap; +use collections::{HashMap, IndexMap}; use fs::Fs; use futures::{ + FutureExt, channel::{mpsc, oneshot}, + future::Shared, stream::FuturesUnordered, }; -use gpui::{App, Context, Entity, SharedString, Task}; +use git::repository::DiffType; +use gpui::{App, AppContext, AsyncApp, Context, Entity, SharedString, Task, WeakEntity}; use language_model::{ - LanguageModel, LanguageModelCompletionEvent, LanguageModelImage, LanguageModelProviderId, - LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool, - LanguageModelToolResult, LanguageModelToolResultContent, LanguageModelToolSchemaFormat, - LanguageModelToolUse, LanguageModelToolUseId, Role, StopReason, + LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelExt, + LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry, LanguageModelRequest, + LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult, + LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse, + LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, +}; +use project::{ + Project, + git_store::{GitStore, RepositoryState}, }; -use project::Project; use prompt_store::ProjectContext; use schemars::{JsonSchema, Schema}; use serde::{Deserialize, Serialize}; use settings::{Settings, update_settings_file}; use smol::stream::StreamExt; -use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc, sync::Arc}; +use std::{ + collections::BTreeMap, + path::Path, + sync::Arc, + time::{Duration, Instant}, +}; use std::{fmt::Write, ops::Range}; use util::{ResultExt, markdown::MarkdownCodeBlock}; use uuid::Uuid; -#[derive( - Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema, -)] -pub struct ThreadId(Arc); - -impl ThreadId { - pub fn new() -> Self { - Self(Uuid::new_v4().to_string().into()) - } -} - -impl std::fmt::Display for ThreadId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From<&str> for ThreadId { - fn from(value: &str) -> Self { - Self(value.into()) - } -} +const TOOL_CANCELED_MESSAGE: &str = "Tool canceled by user"; /// The ID of the user prompt that initiated a request. /// @@ -71,7 +72,22 @@ impl std::fmt::Display for PromptId { } } -#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) const MAX_RETRY_ATTEMPTS: u8 = 4; +pub(crate) const BASE_RETRY_DELAY: Duration = Duration::from_secs(5); + +#[derive(Debug, Clone)] +enum RetryStrategy { + ExponentialBackoff { + initial_delay: Duration, + max_attempts: u8, + }, + Fixed { + delay: Duration, + max_attempts: u8, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Message { User(UserMessage), Agent(AgentMessage), @@ -86,6 +102,18 @@ impl Message { } } + pub fn to_request(&self) -> Vec { + match self { + Message::User(message) => vec![message.to_request()], + Message::Agent(message) => message.to_request(), + Message::Resume => vec![LanguageModelRequestMessage { + role: Role::User, + content: vec!["Continue where you left off".into()], + cache: false, + }], + } + } + pub fn to_markdown(&self) -> String { match self { Message::User(message) => message.to_markdown(), @@ -93,15 +121,22 @@ impl Message { Message::Resume => "[resumed after tool use limit was reached]".into(), } } + + pub fn role(&self) -> Role { + match self { + Message::User(_) | Message::Resume => Role::User, + Message::Agent(_) => Role::Assistant, + } + } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UserMessage { pub id: UserMessageId, pub content: Vec, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum UserMessageContent { Text(String), Mention { uri: MentionUri, content: String }, @@ -146,6 +181,7 @@ impl UserMessage { They are up-to-date and don't need to be re-read.\n\n"; const OPEN_FILES_TAG: &str = ""; + const OPEN_DIRECTORIES_TAG: &str = ""; const OPEN_SYMBOLS_TAG: &str = ""; const OPEN_THREADS_TAG: &str = ""; const OPEN_FETCH_TAG: &str = ""; @@ -153,6 +189,7 @@ impl UserMessage { "\nThe user has specified the following rules that should be applied:\n"; let mut file_context = OPEN_FILES_TAG.to_string(); + let mut directory_context = OPEN_DIRECTORIES_TAG.to_string(); let mut symbol_context = OPEN_SYMBOLS_TAG.to_string(); let mut thread_context = OPEN_THREADS_TAG.to_string(); let mut fetch_context = OPEN_FETCH_TAG.to_string(); @@ -168,17 +205,20 @@ impl UserMessage { } UserMessageContent::Mention { uri, content } => { match uri { - MentionUri::File { abs_path, .. } => { + MentionUri::File { abs_path } => { write!( &mut symbol_context, "\n{}", MarkdownCodeBlock { - tag: &codeblock_tag(&abs_path, None), + tag: &codeblock_tag(abs_path, None), text: &content.to_string(), } ) .ok(); } + MentionUri::Directory { .. } => { + write!(&mut directory_context, "\n{}\n", content).ok(); + } MentionUri::Symbol { path, line_range, .. } @@ -189,8 +229,8 @@ impl UserMessage { &mut rules_context, "\n{}", MarkdownCodeBlock { - tag: &codeblock_tag(&path, Some(line_range)), - text: &content + tag: &codeblock_tag(path, Some(line_range)), + text: content } ) .ok(); @@ -207,7 +247,7 @@ impl UserMessage { "\n{}", MarkdownCodeBlock { tag: "", - text: &content + text: content } ) .ok(); @@ -233,6 +273,13 @@ impl UserMessage { .push(language_model::MessageContent::Text(file_context)); } + if directory_context.len() > OPEN_DIRECTORIES_TAG.len() { + directory_context.push_str("\n"); + message + .content + .push(language_model::MessageContent::Text(directory_context)); + } + if symbol_context.len() > OPEN_SYMBOLS_TAG.len() { symbol_context.push_str("\n"); message @@ -313,9 +360,6 @@ impl AgentMessage { AgentMessageContent::RedactedThinking(_) => { markdown.push_str("\n") } - AgentMessageContent::Image(_) => { - markdown.push_str("\n"); - } AgentMessageContent::ToolUse(tool_use) => { markdown.push_str(&format!( "**Tool Use**: {} (ID: {})\n", @@ -386,9 +430,6 @@ impl AgentMessage { AgentMessageContent::ToolUse(value) => { language_model::MessageContent::ToolUse(value.clone()) } - AgentMessageContent::Image(value) => { - language_model::MessageContent::Image(value.clone()) - } }; assistant_message.content.push(chunk); } @@ -418,13 +459,13 @@ impl AgentMessage { } } -#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AgentMessage { pub content: Vec, pub tool_results: IndexMap, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AgentMessageContent { Text(String), Thinking { @@ -432,17 +473,20 @@ pub enum AgentMessageContent { signature: Option, }, RedactedThinking(String), - Image(LanguageModelImage), ToolUse(LanguageModelToolUse), } #[derive(Debug)] -pub enum AgentResponseEvent { - Text(String), - Thinking(String), +pub enum ThreadEvent { + UserMessage(UserMessage), + AgentText(String), + AgentThinking(String), ToolCall(acp::ToolCall), ToolCallUpdate(acp_thread::ToolCallUpdate), ToolCallAuthorization(ToolCallAuthorization), + TokenUsageUpdate(acp_thread::TokenUsage), + TitleUpdate(SharedString), + Retry(acp_thread::RetryStatus), Stop(acp::StopReason), } @@ -454,8 +498,11 @@ pub struct ToolCallAuthorization { } pub struct Thread { - id: ThreadId, + id: acp::SessionId, prompt_id: PromptId, + updated_at: DateTime, + title: Option, + summary: Option, messages: Vec, completion_mode: CompletionMode, /// Holds the task that handles agent interaction until the end of the turn. @@ -465,19 +512,25 @@ pub struct Thread { pending_message: Option, tools: BTreeMap>, tool_use_limit_reached: bool, + request_token_usage: HashMap, + #[allow(unused)] + cumulative_token_usage: TokenUsage, + #[allow(unused)] + initial_project_snapshot: Shared>>>, context_server_registry: Entity, profile_id: AgentProfileId, - project_context: Rc>, + project_context: Entity, templates: Arc, model: Option>, - project: Entity, - action_log: Entity, + summarization_model: Option>, + pub(crate) project: Entity, + pub(crate) action_log: Entity, } impl Thread { pub fn new( project: Entity, - project_context: Rc>, + project_context: Entity, context_server_registry: Entity, action_log: Entity, templates: Arc, @@ -486,24 +539,327 @@ impl Thread { ) -> Self { let profile_id = AgentSettings::get_global(cx).default_profile.clone(); Self { - id: ThreadId::new(), + id: acp::SessionId(uuid::Uuid::new_v4().to_string().into()), prompt_id: PromptId::new(), + updated_at: Utc::now(), + title: None, + summary: None, messages: Vec::new(), - completion_mode: CompletionMode::Normal, + completion_mode: AgentSettings::get_global(cx).preferred_completion_mode, running_turn: None, pending_message: None, tools: BTreeMap::default(), tool_use_limit_reached: false, + request_token_usage: HashMap::default(), + cumulative_token_usage: TokenUsage::default(), + initial_project_snapshot: { + let project_snapshot = Self::project_snapshot(project.clone(), cx); + cx.foreground_executor() + .spawn(async move { Some(project_snapshot.await) }) + .shared() + }, context_server_registry, profile_id, project_context, templates, model, + summarization_model: None, project, action_log, } } + pub fn id(&self) -> &acp::SessionId { + &self.id + } + + pub fn replay( + &mut self, + cx: &mut Context, + ) -> mpsc::UnboundedReceiver> { + let (tx, rx) = mpsc::unbounded(); + let stream = ThreadEventStream(tx); + for message in &self.messages { + match message { + Message::User(user_message) => stream.send_user_message(user_message), + Message::Agent(assistant_message) => { + for content in &assistant_message.content { + match content { + AgentMessageContent::Text(text) => stream.send_text(text), + AgentMessageContent::Thinking { text, .. } => { + stream.send_thinking(text) + } + AgentMessageContent::RedactedThinking(_) => {} + AgentMessageContent::ToolUse(tool_use) => { + self.replay_tool_call( + tool_use, + assistant_message.tool_results.get(&tool_use.id), + &stream, + cx, + ); + } + } + } + } + Message::Resume => {} + } + } + rx + } + + fn replay_tool_call( + &self, + tool_use: &LanguageModelToolUse, + tool_result: Option<&LanguageModelToolResult>, + stream: &ThreadEventStream, + cx: &mut Context, + ) { + let Some(tool) = self.tools.get(tool_use.name.as_ref()) else { + stream + .0 + .unbounded_send(Ok(ThreadEvent::ToolCall(acp::ToolCall { + id: acp::ToolCallId(tool_use.id.to_string().into()), + title: tool_use.name.to_string(), + kind: acp::ToolKind::Other, + status: acp::ToolCallStatus::Failed, + content: Vec::new(), + locations: Vec::new(), + raw_input: Some(tool_use.input.clone()), + raw_output: None, + }))) + .ok(); + return; + }; + + let title = tool.initial_title(tool_use.input.clone()); + let kind = tool.kind(); + stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone()); + + let output = tool_result + .as_ref() + .and_then(|result| result.output.clone()); + if let Some(output) = output.clone() { + let tool_event_stream = ToolCallEventStream::new( + tool_use.id.clone(), + stream.clone(), + Some(self.project.read(cx).fs().clone()), + ); + tool.replay(tool_use.input.clone(), output, tool_event_stream, cx) + .log_err(); + } + + stream.update_tool_call_fields( + &tool_use.id, + acp::ToolCallUpdateFields { + status: Some(acp::ToolCallStatus::Completed), + raw_output: output, + ..Default::default() + }, + ); + } + + pub fn from_db( + id: acp::SessionId, + db_thread: DbThread, + project: Entity, + project_context: Entity, + context_server_registry: Entity, + action_log: Entity, + templates: Arc, + cx: &mut Context, + ) -> Self { + let profile_id = db_thread + .profile + .unwrap_or_else(|| AgentSettings::get_global(cx).default_profile.clone()); + let model = LanguageModelRegistry::global(cx).update(cx, |registry, cx| { + db_thread + .model + .and_then(|model| { + let model = SelectedModel { + provider: model.provider.clone().into(), + model: model.model.clone().into(), + }; + registry.select_model(&model, cx) + }) + .or_else(|| registry.default_model()) + .map(|model| model.model) + }); + + Self { + id, + prompt_id: PromptId::new(), + title: if db_thread.title.is_empty() { + None + } else { + Some(db_thread.title.clone()) + }, + summary: db_thread.detailed_summary, + messages: db_thread.messages, + completion_mode: db_thread.completion_mode.unwrap_or_default(), + running_turn: None, + pending_message: None, + tools: BTreeMap::default(), + tool_use_limit_reached: false, + request_token_usage: db_thread.request_token_usage.clone(), + cumulative_token_usage: db_thread.cumulative_token_usage, + initial_project_snapshot: Task::ready(db_thread.initial_project_snapshot).shared(), + context_server_registry, + profile_id, + project_context, + templates, + model, + summarization_model: None, + project, + action_log, + updated_at: db_thread.updated_at, + } + } + + pub fn to_db(&self, cx: &App) -> Task { + let initial_project_snapshot = self.initial_project_snapshot.clone(); + let mut thread = DbThread { + title: self.title.clone().unwrap_or_default(), + messages: self.messages.clone(), + updated_at: self.updated_at, + detailed_summary: self.summary.clone(), + initial_project_snapshot: None, + cumulative_token_usage: self.cumulative_token_usage, + request_token_usage: self.request_token_usage.clone(), + model: self.model.as_ref().map(|model| DbLanguageModel { + provider: model.provider_id().to_string(), + model: model.name().0.to_string(), + }), + completion_mode: Some(self.completion_mode), + profile: Some(self.profile_id.clone()), + }; + + cx.background_spawn(async move { + let initial_project_snapshot = initial_project_snapshot.await; + thread.initial_project_snapshot = initial_project_snapshot; + thread + }) + } + + /// Create a snapshot of the current project state including git information and unsaved buffers. + fn project_snapshot( + project: Entity, + cx: &mut Context, + ) -> Task> { + let git_store = project.read(cx).git_store().clone(); + let worktree_snapshots: Vec<_> = project + .read(cx) + .visible_worktrees(cx) + .map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx)) + .collect(); + + cx.spawn(async move |_, cx| { + let worktree_snapshots = futures::future::join_all(worktree_snapshots).await; + + let mut unsaved_buffers = Vec::new(); + cx.update(|app_cx| { + let buffer_store = project.read(app_cx).buffer_store(); + for buffer_handle in buffer_store.read(app_cx).buffers() { + let buffer = buffer_handle.read(app_cx); + if buffer.is_dirty() + && let Some(file) = buffer.file() + { + let path = file.path().to_string_lossy().to_string(); + unsaved_buffers.push(path); + } + } + }) + .ok(); + + Arc::new(ProjectSnapshot { + worktree_snapshots, + unsaved_buffer_paths: unsaved_buffers, + timestamp: Utc::now(), + }) + }) + } + + fn worktree_snapshot( + worktree: Entity, + git_store: Entity, + cx: &App, + ) -> Task { + cx.spawn(async move |cx| { + // Get worktree path and snapshot + let worktree_info = cx.update(|app_cx| { + let worktree = worktree.read(app_cx); + let path = worktree.abs_path().to_string_lossy().to_string(); + let snapshot = worktree.snapshot(); + (path, snapshot) + }); + + let Ok((worktree_path, _snapshot)) = worktree_info else { + return WorktreeSnapshot { + worktree_path: String::new(), + git_state: None, + }; + }; + + let git_state = git_store + .update(cx, |git_store, cx| { + git_store + .repositories() + .values() + .find(|repo| { + repo.read(cx) + .abs_path_to_repo_path(&worktree.read(cx).abs_path()) + .is_some() + }) + .cloned() + }) + .ok() + .flatten() + .map(|repo| { + repo.update(cx, |repo, _| { + let current_branch = + repo.branch.as_ref().map(|branch| branch.name().to_owned()); + repo.send_job(None, |state, _| async move { + let RepositoryState::Local { backend, .. } = state else { + return GitState { + remote_url: None, + head_sha: None, + current_branch, + diff: None, + }; + }; + + let remote_url = backend.remote_url("origin"); + let head_sha = backend.head_sha().await; + let diff = backend.diff(DiffType::HeadToWorktree).await.ok(); + + GitState { + remote_url, + head_sha, + current_branch, + diff, + } + }) + }) + }); + + let git_state = match git_state { + Some(git_state) => match git_state.ok() { + Some(git_state) => git_state.await.ok(), + None => None, + }, + None => None, + }; + + WorktreeSnapshot { + worktree_path, + git_state, + } + }) + } + + pub fn project_context(&self) -> &Entity { + &self.project_context + } + pub fn project(&self) -> &Entity { &self.project } @@ -516,16 +872,27 @@ impl Thread { self.model.as_ref() } - pub fn set_model(&mut self, model: Arc) { + pub fn set_model(&mut self, model: Arc, cx: &mut Context) { self.model = Some(model); + cx.notify() + } + + pub fn set_summarization_model( + &mut self, + model: Option>, + cx: &mut Context, + ) { + self.summarization_model = model; + cx.notify() } pub fn completion_mode(&self) -> CompletionMode { self.completion_mode } - pub fn set_completion_mode(&mut self, mode: CompletionMode) { + pub fn set_completion_mode(&mut self, mode: CompletionMode, cx: &mut Context) { self.completion_mode = mode; + cx.notify() } #[cfg(any(test, feature = "test-support"))] @@ -537,6 +904,32 @@ impl Thread { } } + pub fn add_default_tools(&mut self, cx: &mut Context) { + let language_registry = self.project.read(cx).languages().clone(); + self.add_tool(CopyPathTool::new(self.project.clone())); + self.add_tool(CreateDirectoryTool::new(self.project.clone())); + self.add_tool(DeletePathTool::new( + self.project.clone(), + self.action_log.clone(), + )); + self.add_tool(DiagnosticsTool::new(self.project.clone())); + self.add_tool(EditFileTool::new(cx.weak_entity(), language_registry)); + self.add_tool(FetchTool::new(self.project.read(cx).client().http_client())); + self.add_tool(FindPathTool::new(self.project.clone())); + self.add_tool(GrepTool::new(self.project.clone())); + self.add_tool(ListDirectoryTool::new(self.project.clone())); + self.add_tool(MovePathTool::new(self.project.clone())); + self.add_tool(NowTool); + self.add_tool(OpenTool::new(self.project.clone())); + self.add_tool(ReadFileTool::new( + self.project.clone(), + self.action_log.clone(), + )); + self.add_tool(TerminalTool::new(self.project.clone(), cx)); + self.add_tool(ThinkingTool); + self.add_tool(WebSearchTool); // TODO: Enable this only if it's a zed model. + } + pub fn add_tool(&mut self, tool: impl AgentTool) { self.tools.insert(tool.name(), tool.erase()); } @@ -553,29 +946,58 @@ impl Thread { self.profile_id = profile_id; } - pub fn cancel(&mut self) { + pub fn cancel(&mut self, cx: &mut Context) { if let Some(running_turn) = self.running_turn.take() { running_turn.cancel(); } - self.flush_pending_message(); + self.flush_pending_message(cx); } - pub fn truncate(&mut self, message_id: UserMessageId) -> Result<()> { - self.cancel(); + pub fn update_token_usage(&mut self, update: language_model::TokenUsage) { + let Some(last_user_message) = self.last_user_message() else { + return; + }; + + self.request_token_usage + .insert(last_user_message.id.clone(), update); + } + + pub fn truncate(&mut self, message_id: UserMessageId, cx: &mut Context) -> Result<()> { + self.cancel(cx); let Some(position) = self.messages.iter().position( |msg| matches!(msg, Message::User(UserMessage { id, .. }) if id == &message_id), ) else { return Err(anyhow!("Message not found")); }; - self.messages.truncate(position); + + for message in self.messages.drain(position..) { + match message { + Message::User(message) => { + self.request_token_usage.remove(&message.id); + } + Message::Agent(_) | Message::Resume => {} + } + } + self.summary = None; + cx.notify(); Ok(()) } + pub fn latest_token_usage(&self) -> Option { + let last_user_message = self.last_user_message()?; + let tokens = self.request_token_usage.get(&last_user_message.id)?; + let model = self.model.clone()?; + + Some(acp_thread::TokenUsage { + max_tokens: model.max_token_count_for_mode(self.completion_mode.into()), + used_tokens: tokens.total_tokens(), + }) + } + pub fn resume( &mut self, cx: &mut Context, - ) -> Result>> { - anyhow::ensure!(self.model.is_some(), "Model not set"); + ) -> Result>> { anyhow::ensure!( self.tool_use_limit_reached, "can only resume after tool use limit is reached" @@ -596,7 +1018,7 @@ impl Thread { id: UserMessageId, content: impl IntoIterator, cx: &mut Context, - ) -> Result>> + ) -> Result>> where T: Into, { @@ -619,22 +1041,20 @@ impl Thread { fn run_turn( &mut self, cx: &mut Context, - ) -> Result>> { - self.cancel(); + ) -> Result>> { + self.cancel(cx); - let model = self - .model() - .cloned() - .context("No language model configured")?; - let (events_tx, events_rx) = mpsc::unbounded::>(); - let event_stream = AgentResponseEventStream(events_tx); + let model = self.model.clone().context("No language model configured")?; + let (events_tx, events_rx) = mpsc::unbounded::>(); + let event_stream = ThreadEventStream(events_tx); let message_ix = self.messages.len().saturating_sub(1); self.tool_use_limit_reached = false; + self.summary = None; self.running_turn = Some(RunningTurn { event_stream: event_stream.clone(), _task: cx.spawn(async move |this, cx| { log::info!("Starting agent turn execution"); - let turn_result: Result<()> = async { + let turn_result: Result = async { let mut completion_intent = CompletionIntent::UserPrompt; loop { log::debug!( @@ -646,43 +1066,29 @@ impl Thread { })??; log::info!("Calling model.stream_completion"); - let mut events = model.stream_completion(request, cx).await?; - log::debug!("Stream completion started successfully"); let mut tool_use_limit_reached = false; - let mut tool_uses = FuturesUnordered::new(); - while let Some(event) = events.next().await { - match event? { - LanguageModelCompletionEvent::StatusUpdate( - CompletionRequestStatus::ToolUseLimitReached, - ) => { - tool_use_limit_reached = true; - } - LanguageModelCompletionEvent::Stop(reason) => { - event_stream.send_stop(reason); - if reason == StopReason::Refusal { - this.update(cx, |this, _cx| { - this.flush_pending_message(); - this.messages.truncate(message_ix); - })?; - return Ok(()); - } - } - event => { - log::trace!("Received completion event: {:?}", event); - this.update(cx, |this, cx| { - tool_uses.extend(this.handle_streamed_completion_event( - event, - &event_stream, - cx, - )); - }) - .ok(); - } - } + let mut refused = false; + let mut reached_max_tokens = false; + let mut tool_uses = Self::stream_completion_with_retries( + this.clone(), + model.clone(), + request, + &event_stream, + &mut tool_use_limit_reached, + &mut refused, + &mut reached_max_tokens, + cx, + ) + .await?; + + if refused { + return Ok(StopReason::Refusal); + } else if reached_max_tokens { + return Ok(StopReason::MaxTokens); } - let used_tools = tool_uses.is_empty(); + let end_turn = tool_uses.is_empty(); while let Some(tool_result) = tool_uses.next().await { log::info!("Tool finished {:?}", tool_result); @@ -710,38 +1116,164 @@ impl Thread { log::info!("Tool use limit reached, completing turn"); this.update(cx, |this, _cx| this.tool_use_limit_reached = true)?; return Err(language_model::ToolUseLimitReachedError.into()); - } else if used_tools { + } else if end_turn { log::info!("No tool uses found, completing turn"); - return Ok(()); + return Ok(StopReason::EndTurn); } else { - this.update(cx, |this, _| this.flush_pending_message())?; + this.update(cx, |this, cx| this.flush_pending_message(cx))?; completion_intent = CompletionIntent::ToolResults; } } } .await; + _ = this.update(cx, |this, cx| this.flush_pending_message(cx)); - if let Err(error) = turn_result { - log::error!("Turn execution failed: {:?}", error); - event_stream.send_error(error); - } else { - log::info!("Turn execution completed successfully"); + match turn_result { + Ok(reason) => { + log::info!("Turn execution completed: {:?}", reason); + + let update_title = this + .update(cx, |this, cx| this.update_title(&event_stream, cx)) + .ok() + .flatten(); + if let Some(update_title) = update_title { + update_title.await.context("update title failed").log_err(); + } + + event_stream.send_stop(reason); + if reason == StopReason::Refusal { + _ = this.update(cx, |this, _| this.messages.truncate(message_ix)); + } + } + Err(error) => { + log::error!("Turn execution failed: {:?}", error); + event_stream.send_error(error); + } } - this.update(cx, |this, _| { - this.flush_pending_message(); - this.running_turn.take(); - }) - .ok(); + _ = this.update(cx, |this, _| this.running_turn.take()); }), }); Ok(events_rx) } - pub fn build_system_message(&self) -> LanguageModelRequestMessage { + async fn stream_completion_with_retries( + this: WeakEntity, + model: Arc, + request: LanguageModelRequest, + event_stream: &ThreadEventStream, + tool_use_limit_reached: &mut bool, + refusal: &mut bool, + max_tokens_reached: &mut bool, + cx: &mut AsyncApp, + ) -> Result>> { + log::debug!("Stream completion started successfully"); + + let mut attempt = None; + 'retry: loop { + let mut events = model.stream_completion(request.clone(), cx).await?; + let mut tool_uses = FuturesUnordered::new(); + while let Some(event) = events.next().await { + match event { + Ok(LanguageModelCompletionEvent::StatusUpdate( + CompletionRequestStatus::ToolUseLimitReached, + )) => { + *tool_use_limit_reached = true; + } + Ok(LanguageModelCompletionEvent::UsageUpdate(token_usage)) => { + let usage = acp_thread::TokenUsage { + max_tokens: model.max_token_count_for_mode( + request + .mode + .unwrap_or(cloud_llm_client::CompletionMode::Normal), + ), + used_tokens: token_usage.total_tokens(), + }; + + this.update(cx, |this, _cx| this.update_token_usage(token_usage)) + .ok(); + + event_stream.send_token_usage_update(usage); + } + Ok(LanguageModelCompletionEvent::Stop(StopReason::Refusal)) => { + *refusal = true; + return Ok(FuturesUnordered::default()); + } + Ok(LanguageModelCompletionEvent::Stop(StopReason::MaxTokens)) => { + *max_tokens_reached = true; + return Ok(FuturesUnordered::default()); + } + Ok(LanguageModelCompletionEvent::Stop( + StopReason::ToolUse | StopReason::EndTurn, + )) => break, + Ok(event) => { + log::trace!("Received completion event: {:?}", event); + this.update(cx, |this, cx| { + tool_uses.extend(this.handle_streamed_completion_event( + event, + event_stream, + cx, + )); + }) + .ok(); + } + Err(error) => { + let completion_mode = + this.read_with(cx, |thread, _cx| thread.completion_mode())?; + if completion_mode == CompletionMode::Normal { + return Err(error.into()); + } + + let Some(strategy) = Self::retry_strategy_for(&error) else { + return Err(error.into()); + }; + + let max_attempts = match &strategy { + RetryStrategy::ExponentialBackoff { max_attempts, .. } => *max_attempts, + RetryStrategy::Fixed { max_attempts, .. } => *max_attempts, + }; + + let attempt = attempt.get_or_insert(0u8); + + *attempt += 1; + + let attempt = *attempt; + if attempt > max_attempts { + return Err(error.into()); + } + + let delay = match &strategy { + RetryStrategy::ExponentialBackoff { initial_delay, .. } => { + let delay_secs = + initial_delay.as_secs() * 2u64.pow((attempt - 1) as u32); + Duration::from_secs(delay_secs) + } + RetryStrategy::Fixed { delay, .. } => *delay, + }; + log::debug!("Retry attempt {attempt} with delay {delay:?}"); + + event_stream.send_retry(acp_thread::RetryStatus { + last_error: error.to_string().into(), + attempt: attempt as usize, + max_attempts: max_attempts as usize, + started_at: Instant::now(), + duration: delay, + }); + + cx.background_executor().timer(delay).await; + continue 'retry; + } + } + } + + return Ok(tool_uses); + } + } + + pub fn build_system_message(&self, cx: &App) -> LanguageModelRequestMessage { log::debug!("Building system message"); let prompt = SystemPromptTemplate { - project: &self.project_context.borrow(), + project: self.project_context.read(cx), available_tools: self.tools.keys().cloned().collect(), } .render(&self.templates) @@ -761,7 +1293,7 @@ impl Thread { fn handle_streamed_completion_event( &mut self, event: LanguageModelCompletionEvent, - event_stream: &AgentResponseEventStream, + event_stream: &ThreadEventStream, cx: &mut Context, ) -> Option> { log::trace!("Handling streamed completion event: {:?}", event); @@ -769,7 +1301,7 @@ impl Thread { match event { StartMessage { .. } => { - self.flush_pending_message(); + self.flush_pending_message(cx); self.pending_message = Some(AgentMessage::default()); } Text(new_text) => self.handle_text_event(new_text, event_stream, cx), @@ -803,7 +1335,7 @@ impl Thread { fn handle_text_event( &mut self, new_text: String, - event_stream: &AgentResponseEventStream, + event_stream: &ThreadEventStream, cx: &mut Context, ) { event_stream.send_text(&new_text); @@ -824,7 +1356,7 @@ impl Thread { &mut self, new_text: String, new_signature: Option, - event_stream: &AgentResponseEventStream, + event_stream: &ThreadEventStream, cx: &mut Context, ) { event_stream.send_thinking(&new_text); @@ -856,7 +1388,7 @@ impl Thread { fn handle_tool_use_event( &mut self, tool_use: LanguageModelToolUse, - event_stream: &AgentResponseEventStream, + event_stream: &ThreadEventStream, cx: &mut Context, ) -> Option> { cx.notify(); @@ -871,7 +1403,7 @@ impl Thread { // Ensure the last message ends in the current tool use let last_message = self.pending_message(); - let push_new_tool_use = last_message.content.last_mut().map_or(true, |content| { + let push_new_tool_use = last_message.content.last_mut().is_none_or(|content| { if let AgentMessageContent::ToolUse(last_tool_use) = content { if last_tool_use.id == tool_use.id { *last_tool_use = tool_use.clone(); @@ -923,17 +1455,17 @@ impl Thread { status: Some(acp::ToolCallStatus::InProgress), ..Default::default() }); - let supports_images = self.model().map_or(false, |model| model.supports_images()); + let supports_images = self.model().is_some_and(|model| model.supports_images()); let tool_result = tool.run(tool_use.input, tool_event_stream, cx); log::info!("Running tool {}", tool_use.name); Some(cx.foreground_executor().spawn(async move { let tool_result = tool_result.await.and_then(|output| { - if let LanguageModelToolResultContent::Image(_) = &output.llm_output { - if !supports_images { - return Err(anyhow!( - "Attempted to read an image, but this model doesn't support it.", - )); - } + if let LanguageModelToolResultContent::Image(_) = &output.llm_output + && !supports_images + { + return Err(anyhow!( + "Attempted to read an image, but this model doesn't support it.", + )); } Ok(output) }); @@ -974,11 +1506,152 @@ impl Thread { } } + pub fn title(&self) -> SharedString { + self.title.clone().unwrap_or("New Thread".into()) + } + + pub fn summary(&mut self, cx: &mut Context) -> Task> { + if let Some(summary) = self.summary.as_ref() { + return Task::ready(Ok(summary.clone())); + } + let Some(model) = self.summarization_model.clone() else { + return Task::ready(Err(anyhow!("No summarization model available"))); + }; + let mut request = LanguageModelRequest { + intent: Some(CompletionIntent::ThreadSummarization), + temperature: AgentSettings::temperature_for_model(&model, cx), + ..Default::default() + }; + + for message in &self.messages { + request.messages.extend(message.to_request()); + } + + request.messages.push(LanguageModelRequestMessage { + role: Role::User, + content: vec![SUMMARIZE_THREAD_DETAILED_PROMPT.into()], + cache: false, + }); + cx.spawn(async move |this, cx| { + let mut summary = String::new(); + let mut messages = model.stream_completion(request, cx).await?; + while let Some(event) = messages.next().await { + let event = event?; + let text = match event { + LanguageModelCompletionEvent::Text(text) => text, + LanguageModelCompletionEvent::StatusUpdate( + CompletionRequestStatus::UsageUpdated { .. }, + ) => { + // this.update(cx, |thread, cx| { + // thread.update_model_request_usage(amount as u32, limit, cx); + // })?; + // TODO: handle usage update + continue; + } + _ => continue, + }; + + let mut lines = text.lines(); + summary.extend(lines.next()); + } + + log::info!("Setting summary: {}", summary); + let summary = SharedString::from(summary); + + this.update(cx, |this, cx| { + this.summary = Some(summary.clone()); + cx.notify() + })?; + + Ok(summary) + }) + } + + fn update_title( + &mut self, + event_stream: &ThreadEventStream, + cx: &mut Context, + ) -> Option>> { + if self.title.is_some() { + log::debug!("Skipping title generation because we already have one."); + return None; + } + + log::info!( + "Generating title with model: {:?}", + self.summarization_model.as_ref().map(|model| model.name()) + ); + let model = self.summarization_model.clone()?; + let event_stream = event_stream.clone(); + let mut request = LanguageModelRequest { + intent: Some(CompletionIntent::ThreadSummarization), + temperature: AgentSettings::temperature_for_model(&model, cx), + ..Default::default() + }; + + for message in &self.messages { + request.messages.extend(message.to_request()); + } + + request.messages.push(LanguageModelRequestMessage { + role: Role::User, + content: vec![SUMMARIZE_THREAD_PROMPT.into()], + cache: false, + }); + Some(cx.spawn(async move |this, cx| { + let mut title = String::new(); + let mut messages = model.stream_completion(request, cx).await?; + while let Some(event) = messages.next().await { + let event = event?; + let text = match event { + LanguageModelCompletionEvent::Text(text) => text, + LanguageModelCompletionEvent::StatusUpdate( + CompletionRequestStatus::UsageUpdated { .. }, + ) => { + // this.update(cx, |thread, cx| { + // thread.update_model_request_usage(amount as u32, limit, cx); + // })?; + // TODO: handle usage update + continue; + } + _ => continue, + }; + + let mut lines = text.lines(); + title.extend(lines.next()); + + // Stop if the LLM generated multiple lines. + if lines.next().is_some() { + break; + } + } + + log::info!("Setting title: {}", title); + + this.update(cx, |this, cx| { + let title = SharedString::from(title); + event_stream.send_title_update(title.clone()); + this.title = Some(title); + cx.notify(); + }) + })) + } + fn last_user_message(&self) -> Option<&UserMessage> { + self.messages + .iter() + .rev() + .find_map(|message| match message { + Message::User(user_message) => Some(user_message), + Message::Agent(_) => None, + Message::Resume => None, + }) + } + fn pending_message(&mut self) -> &mut AgentMessage { self.pending_message.get_or_insert_default() } - fn flush_pending_message(&mut self) { + fn flush_pending_message(&mut self, cx: &mut Context) { let Some(mut message) = self.pending_message.take() else { return; }; @@ -995,9 +1668,7 @@ impl Thread { tool_use_id: tool_use.id.clone(), tool_name: tool_use.name.clone(), is_error: true, - content: LanguageModelToolResultContent::Text( - "Tool canceled by user".into(), - ), + content: LanguageModelToolResultContent::Text(TOOL_CANCELED_MESSAGE.into()), output: None, }, ); @@ -1005,6 +1676,9 @@ impl Thread { } self.messages.push(Message::Agent(message)); + self.updated_at = Utc::now(); + self.summary = None; + cx.notify() } pub(crate) fn build_completion_request( @@ -1018,7 +1692,7 @@ impl Thread { log::debug!("Completion intent: {:?}", completion_intent); log::debug!("Completion mode: {:?}", self.completion_mode); - let messages = self.build_request_messages(); + let messages = self.build_request_messages(cx); log::info!("Request will include {} messages", messages.len()); let tools = if let Some(tools) = self.tools(cx).log_err() { @@ -1048,7 +1722,7 @@ impl Thread { tools, tool_choice: None, stop: Vec::new(), - temperature: AgentSettings::temperature_for_model(&model, cx), + temperature: AgentSettings::temperature_for_model(model, cx), thinking_allowed: true, }; @@ -1089,22 +1763,14 @@ impl Thread { ))) } - fn build_request_messages(&self) -> Vec { + fn build_request_messages(&self, cx: &App) -> Vec { log::trace!( "Building request messages from {} thread messages", self.messages.len() ); - let mut messages = vec![self.build_system_message()]; + let mut messages = vec![self.build_system_message(cx)]; for message in &self.messages { - match message { - Message::User(message) => messages.push(message.to_request()), - Message::Agent(message) => messages.extend(message.to_request()), - Message::Resume => messages.push(LanguageModelRequestMessage { - role: Role::User, - content: vec!["Continue where you left off".into()], - cache: false, - }), - } + messages.extend(message.to_request()); } if let Some(message) = self.pending_message.as_ref() { @@ -1142,6 +1808,113 @@ impl Thread { fn advance_prompt_id(&mut self) { self.prompt_id = PromptId::new(); } + + fn retry_strategy_for(error: &LanguageModelCompletionError) -> Option { + use LanguageModelCompletionError::*; + use http_client::StatusCode; + + // General strategy here: + // - If retrying won't help (e.g. invalid API key or payload too large), return None so we don't retry at all. + // - If it's a time-based issue (e.g. server overloaded, rate limit exceeded), retry up to 4 times with exponential backoff. + // - If it's an issue that *might* be fixed by retrying (e.g. internal server error), retry up to 3 times. + match error { + HttpResponseError { + status_code: StatusCode::TOO_MANY_REQUESTS, + .. + } => Some(RetryStrategy::ExponentialBackoff { + initial_delay: BASE_RETRY_DELAY, + max_attempts: MAX_RETRY_ATTEMPTS, + }), + ServerOverloaded { retry_after, .. } | RateLimitExceeded { retry_after, .. } => { + Some(RetryStrategy::Fixed { + delay: retry_after.unwrap_or(BASE_RETRY_DELAY), + max_attempts: MAX_RETRY_ATTEMPTS, + }) + } + UpstreamProviderError { + status, + retry_after, + .. + } => match *status { + StatusCode::TOO_MANY_REQUESTS | StatusCode::SERVICE_UNAVAILABLE => { + Some(RetryStrategy::Fixed { + delay: retry_after.unwrap_or(BASE_RETRY_DELAY), + max_attempts: MAX_RETRY_ATTEMPTS, + }) + } + StatusCode::INTERNAL_SERVER_ERROR => Some(RetryStrategy::Fixed { + delay: retry_after.unwrap_or(BASE_RETRY_DELAY), + // Internal Server Error could be anything, retry up to 3 times. + max_attempts: 3, + }), + status => { + // There is no StatusCode variant for the unofficial HTTP 529 ("The service is overloaded"), + // but we frequently get them in practice. See https://http.dev/529 + if status.as_u16() == 529 { + Some(RetryStrategy::Fixed { + delay: retry_after.unwrap_or(BASE_RETRY_DELAY), + max_attempts: MAX_RETRY_ATTEMPTS, + }) + } else { + Some(RetryStrategy::Fixed { + delay: retry_after.unwrap_or(BASE_RETRY_DELAY), + max_attempts: 2, + }) + } + } + }, + ApiInternalServerError { .. } => Some(RetryStrategy::Fixed { + delay: BASE_RETRY_DELAY, + max_attempts: 3, + }), + ApiReadResponseError { .. } + | HttpSend { .. } + | DeserializeResponse { .. } + | BadRequestFormat { .. } => Some(RetryStrategy::Fixed { + delay: BASE_RETRY_DELAY, + max_attempts: 3, + }), + // Retrying these errors definitely shouldn't help. + HttpResponseError { + status_code: + StatusCode::PAYLOAD_TOO_LARGE | StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED, + .. + } + | AuthenticationError { .. } + | PermissionError { .. } + | NoApiKey { .. } + | ApiEndpointNotFound { .. } + | PromptTooLarge { .. } => None, + // These errors might be transient, so retry them + SerializeRequest { .. } | BuildRequestBody { .. } => Some(RetryStrategy::Fixed { + delay: BASE_RETRY_DELAY, + max_attempts: 1, + }), + // Retry all other 4xx and 5xx errors once. + HttpResponseError { status_code, .. } + if status_code.is_client_error() || status_code.is_server_error() => + { + Some(RetryStrategy::Fixed { + delay: BASE_RETRY_DELAY, + max_attempts: 3, + }) + } + Other(err) + if err.is::() + || err.is::() => + { + // Retrying won't help for Payment Required or Model Request Limit errors (where + // the user must upgrade to usage-based billing to get more requests, or else wait + // for a significant amount of time for the request limit to reset). + None + } + // Conservatively assume that any other errors are non-retryable + HttpResponseError { .. } | Other(..) => Some(RetryStrategy::Fixed { + delay: BASE_RETRY_DELAY, + max_attempts: 2, + }), + } + } } struct RunningTurn { @@ -1151,7 +1924,7 @@ struct RunningTurn { _task: Task<()>, /// The current event stream for the running turn. Used to report a final /// cancellation event if we cancel the turn. - event_stream: AgentResponseEventStream, + event_stream: ThreadEventStream, } impl RunningTurn { @@ -1186,8 +1959,8 @@ where fn initial_title(&self, input: Result) -> SharedString; /// Returns the JSON schema that describes the tool's input. - fn input_schema(&self) -> Schema { - schemars::schema_for!(Self::Input) + fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Schema { + crate::tool_schema::root_schema_for::(format) } /// Some tools rely on a provider for the underlying billing or other reasons. @@ -1204,6 +1977,17 @@ where cx: &mut App, ) -> Task>; + /// Emits events for a previous execution of the tool. + fn replay( + &self, + _input: Self::Input, + _output: Self::Output, + _event_stream: ToolCallEventStream, + _cx: &mut App, + ) -> Result<()> { + Ok(()) + } + fn erase(self) -> Arc { Arc::new(Erased(Arc::new(self))) } @@ -1231,6 +2015,13 @@ pub trait AnyAgentTool { event_stream: ToolCallEventStream, cx: &mut App, ) -> Task>; + fn replay( + &self, + input: serde_json::Value, + output: serde_json::Value, + event_stream: ToolCallEventStream, + cx: &mut App, + ) -> Result<()>; } impl AnyAgentTool for Erased> @@ -1255,7 +2046,7 @@ where } fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result { - let mut json = serde_json::to_value(self.0.input_schema())?; + let mut json = serde_json::to_value(self.0.input_schema(format))?; adapt_schema_to_format(&mut json, format)?; Ok(json) } @@ -1282,21 +2073,45 @@ where }) }) } + + fn replay( + &self, + input: serde_json::Value, + output: serde_json::Value, + event_stream: ToolCallEventStream, + cx: &mut App, + ) -> Result<()> { + let input = serde_json::from_value(input)?; + let output = serde_json::from_value(output)?; + self.0.replay(input, output, event_stream, cx) + } } #[derive(Clone)] -struct AgentResponseEventStream(mpsc::UnboundedSender>); +struct ThreadEventStream(mpsc::UnboundedSender>); + +impl ThreadEventStream { + fn send_title_update(&self, text: SharedString) { + self.0 + .unbounded_send(Ok(ThreadEvent::TitleUpdate(text))) + .ok(); + } + + fn send_user_message(&self, message: &UserMessage) { + self.0 + .unbounded_send(Ok(ThreadEvent::UserMessage(message.clone()))) + .ok(); + } -impl AgentResponseEventStream { fn send_text(&self, text: &str) { self.0 - .unbounded_send(Ok(AgentResponseEvent::Text(text.to_string()))) + .unbounded_send(Ok(ThreadEvent::AgentText(text.to_string()))) .ok(); } fn send_thinking(&self, text: &str) { self.0 - .unbounded_send(Ok(AgentResponseEvent::Thinking(text.to_string()))) + .unbounded_send(Ok(ThreadEvent::AgentThinking(text.to_string()))) .ok(); } @@ -1308,7 +2123,7 @@ impl AgentResponseEventStream { input: serde_json::Value, ) { self.0 - .unbounded_send(Ok(AgentResponseEvent::ToolCall(Self::initial_tool_call( + .unbounded_send(Ok(ThreadEvent::ToolCall(Self::initial_tool_call( id, title.to_string(), kind, @@ -1341,7 +2156,7 @@ impl AgentResponseEventStream { fields: acp::ToolCallUpdateFields, ) { self.0 - .unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate( + .unbounded_send(Ok(ThreadEvent::ToolCallUpdate( acp::ToolCallUpdate { id: acp::ToolCallId(tool_use_id.to_string().into()), fields, @@ -1351,21 +2166,31 @@ impl AgentResponseEventStream { .ok(); } + fn send_token_usage_update(&self, usage: acp_thread::TokenUsage) { + self.0 + .unbounded_send(Ok(ThreadEvent::TokenUsageUpdate(usage))) + .ok(); + } + + fn send_retry(&self, status: acp_thread::RetryStatus) { + self.0.unbounded_send(Ok(ThreadEvent::Retry(status))).ok(); + } + fn send_stop(&self, reason: StopReason) { match reason { StopReason::EndTurn => { self.0 - .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::EndTurn))) + .unbounded_send(Ok(ThreadEvent::Stop(acp::StopReason::EndTurn))) .ok(); } StopReason::MaxTokens => { self.0 - .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::MaxTokens))) + .unbounded_send(Ok(ThreadEvent::Stop(acp::StopReason::MaxTokens))) .ok(); } StopReason::Refusal => { self.0 - .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::Refusal))) + .unbounded_send(Ok(ThreadEvent::Stop(acp::StopReason::Refusal))) .ok(); } StopReason::ToolUse => {} @@ -1374,7 +2199,7 @@ impl AgentResponseEventStream { fn send_canceled(&self) { self.0 - .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::Canceled))) + .unbounded_send(Ok(ThreadEvent::Stop(acp::StopReason::Canceled))) .ok(); } @@ -1386,24 +2211,23 @@ impl AgentResponseEventStream { #[derive(Clone)] pub struct ToolCallEventStream { tool_use_id: LanguageModelToolUseId, - stream: AgentResponseEventStream, + stream: ThreadEventStream, fs: Option>, } impl ToolCallEventStream { #[cfg(test)] pub fn test() -> (Self, ToolCallEventStreamReceiver) { - let (events_tx, events_rx) = mpsc::unbounded::>(); + let (events_tx, events_rx) = mpsc::unbounded::>(); - let stream = - ToolCallEventStream::new("test_id".into(), AgentResponseEventStream(events_tx), None); + let stream = ToolCallEventStream::new("test_id".into(), ThreadEventStream(events_tx), None); (stream, ToolCallEventStreamReceiver(events_rx)) } fn new( tool_use_id: LanguageModelToolUseId, - stream: AgentResponseEventStream, + stream: ThreadEventStream, fs: Option>, ) -> Self { Self { @@ -1421,7 +2245,7 @@ impl ToolCallEventStream { pub fn update_diff(&self, diff: Entity) { self.stream .0 - .unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate( + .unbounded_send(Ok(ThreadEvent::ToolCallUpdate( acp_thread::ToolCallUpdateDiff { id: acp::ToolCallId(self.tool_use_id.to_string().into()), diff, @@ -1434,7 +2258,7 @@ impl ToolCallEventStream { pub fn update_terminal(&self, terminal: Entity) { self.stream .0 - .unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate( + .unbounded_send(Ok(ThreadEvent::ToolCallUpdate( acp_thread::ToolCallUpdateTerminal { id: acp::ToolCallId(self.tool_use_id.to_string().into()), terminal, @@ -1452,7 +2276,7 @@ impl ToolCallEventStream { let (response_tx, response_rx) = oneshot::channel(); self.stream .0 - .unbounded_send(Ok(AgentResponseEvent::ToolCallAuthorization( + .unbounded_send(Ok(ThreadEvent::ToolCallAuthorization( ToolCallAuthorization { tool_call: acp::ToolCallUpdate { id: acp::ToolCallId(self.tool_use_id.to_string().into()), @@ -1502,13 +2326,13 @@ impl ToolCallEventStream { } #[cfg(test)] -pub struct ToolCallEventStreamReceiver(mpsc::UnboundedReceiver>); +pub struct ToolCallEventStreamReceiver(mpsc::UnboundedReceiver>); #[cfg(test)] impl ToolCallEventStreamReceiver { pub async fn expect_authorization(&mut self) -> ToolCallAuthorization { let event = self.0.next().await; - if let Some(Ok(AgentResponseEvent::ToolCallAuthorization(auth))) = event { + if let Some(Ok(ThreadEvent::ToolCallAuthorization(auth))) = event { auth } else { panic!("Expected ToolCallAuthorization but got: {:?}", event); @@ -1517,9 +2341,9 @@ impl ToolCallEventStreamReceiver { pub async fn expect_terminal(&mut self) -> Entity { let event = self.0.next().await; - if let Some(Ok(AgentResponseEvent::ToolCallUpdate( - acp_thread::ToolCallUpdate::UpdateTerminal(update), - ))) = event + if let Some(Ok(ThreadEvent::ToolCallUpdate(acp_thread::ToolCallUpdate::UpdateTerminal( + update, + )))) = event { update.terminal } else { @@ -1530,7 +2354,7 @@ impl ToolCallEventStreamReceiver { #[cfg(test)] impl std::ops::Deref for ToolCallEventStreamReceiver { - type Target = mpsc::UnboundedReceiver>; + type Target = mpsc::UnboundedReceiver>; fn deref(&self) -> &Self::Target { &self.0 @@ -1599,6 +2423,38 @@ impl From for UserMessageContent { } } +impl From for acp::ContentBlock { + fn from(content: UserMessageContent) -> Self { + match content { + UserMessageContent::Text(text) => acp::ContentBlock::Text(acp::TextContent { + text, + annotations: None, + }), + UserMessageContent::Image(image) => acp::ContentBlock::Image(acp::ImageContent { + data: image.source.to_string(), + mime_type: "image/png".to_string(), + annotations: None, + uri: None, + }), + UserMessageContent::Mention { uri, content } => { + acp::ContentBlock::ResourceLink(acp::ResourceLink { + uri: uri.to_uri().to_string(), + name: uri.name(), + annotations: None, + description: if content.is_empty() { + None + } else { + Some(content) + }, + mime_type: None, + size: None, + title: None, + }) + } + } + } +} + fn convert_image(image_content: acp::ImageContent) -> LanguageModelImage { LanguageModelImage { source: image_content.data.into(), diff --git a/crates/agent2/src/tool_schema.rs b/crates/agent2/src/tool_schema.rs new file mode 100644 index 0000000000..f608336b41 --- /dev/null +++ b/crates/agent2/src/tool_schema.rs @@ -0,0 +1,43 @@ +use language_model::LanguageModelToolSchemaFormat; +use schemars::{ + JsonSchema, Schema, + generate::SchemaSettings, + transform::{Transform, transform_subschemas}, +}; + +pub(crate) fn root_schema_for(format: LanguageModelToolSchemaFormat) -> Schema { + let mut generator = match format { + LanguageModelToolSchemaFormat::JsonSchema => SchemaSettings::draft07().into_generator(), + LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::openapi3() + .with(|settings| { + settings.meta_schema = None; + settings.inline_subschemas = true; + }) + .with_transform(ToJsonSchemaSubsetTransform) + .into_generator(), + }; + generator.root_schema_for::() +} + +#[derive(Debug, Clone)] +struct ToJsonSchemaSubsetTransform; + +impl Transform for ToJsonSchemaSubsetTransform { + fn transform(&mut self, schema: &mut Schema) { + // Ensure that the type field is not an array, this happens when we use + // Option, the type will be [T, "null"]. + if let Some(type_field) = schema.get_mut("type") + && let Some(types) = type_field.as_array() + && let Some(first_type) = types.first() + { + *type_field = first_type.clone(); + } + + // oneOf is not supported, use anyOf instead + if let Some(one_of) = schema.remove("oneOf") { + schema.insert("anyOf".to_string(), one_of); + } + + transform_subschemas(self, schema); + } +} diff --git a/crates/agent2/src/tools/context_server_registry.rs b/crates/agent2/src/tools/context_server_registry.rs index db39e9278c..69c4221a81 100644 --- a/crates/agent2/src/tools/context_server_registry.rs +++ b/crates/agent2/src/tools/context_server_registry.rs @@ -103,7 +103,7 @@ impl ContextServerRegistry { self.reload_tools_for_server(server_id.clone(), cx); } ContextServerStatus::Stopped | ContextServerStatus::Error(_) => { - self.registered_servers.remove(&server_id); + self.registered_servers.remove(server_id); cx.notify(); } } @@ -228,4 +228,14 @@ impl AnyAgentTool for ContextServerTool { }) }) } + + fn replay( + &self, + _input: serde_json::Value, + _output: serde_json::Value, + _event_stream: ToolCallEventStream, + _cx: &mut App, + ) -> Result<()> { + Ok(()) + } } diff --git a/crates/agent2/src/tools/edit_file_tool.rs b/crates/agent2/src/tools/edit_file_tool.rs index c55e503d76..a87699bd12 100644 --- a/crates/agent2/src/tools/edit_file_tool.rs +++ b/crates/agent2/src/tools/edit_file_tool.rs @@ -5,10 +5,10 @@ use anyhow::{Context as _, Result, anyhow}; use assistant_tools::edit_agent::{EditAgent, EditAgentOutput, EditAgentOutputEvent, EditFormat}; use cloud_llm_client::CompletionIntent; use collections::HashSet; -use gpui::{App, AppContext, AsyncApp, Entity, Task}; +use gpui::{App, AppContext, AsyncApp, Entity, Task, WeakEntity}; use indoc::formatdoc; -use language::ToPoint; use language::language_settings::{self, FormatOnSave}; +use language::{LanguageRegistry, ToPoint}; use language_model::LanguageModelToolResultContent; use paths; use project::lsp_store::{FormatTrigger, LspFormatTarget}; @@ -98,11 +98,13 @@ pub enum EditFileMode { #[derive(Debug, Serialize, Deserialize)] pub struct EditFileToolOutput { + #[serde(alias = "original_path")] input_path: PathBuf, - project_path: PathBuf, new_text: String, old_text: Arc, + #[serde(default)] diff: String, + #[serde(alias = "raw_output")] edit_agent_output: EditAgentOutput, } @@ -122,12 +124,16 @@ impl From for LanguageModelToolResultContent { } pub struct EditFileTool { - thread: Entity, + thread: WeakEntity, + language_registry: Arc, } impl EditFileTool { - pub fn new(thread: Entity) -> Self { - Self { thread } + pub fn new(thread: WeakEntity, language_registry: Arc) -> Self { + Self { + thread, + language_registry, + } } fn authorize( @@ -156,19 +162,22 @@ impl EditFileTool { // It's also possible that the global config dir is configured to be inside the project, // so check for that edge case too. - if let Ok(canonical_path) = std::fs::canonicalize(&input.path) { - if canonical_path.starts_with(paths::config_dir()) { - return event_stream.authorize( - format!("{} (global settings)", input.display_description), - cx, - ); - } + if let Ok(canonical_path) = std::fs::canonicalize(&input.path) + && canonical_path.starts_with(paths::config_dir()) + { + return event_stream.authorize( + format!("{} (global settings)", input.display_description), + cx, + ); } // Check if path is inside the global config directory // First check if it's already inside project - if not, try to canonicalize - let thread = self.thread.read(cx); - let project_path = thread.project().read(cx).find_project_path(&input.path, cx); + let Ok(project_path) = self.thread.read_with(cx, |thread, cx| { + thread.project().read(cx).find_project_path(&input.path, cx) + }) else { + return Task::ready(Err(anyhow!("thread was dropped"))); + }; // If the path is inside the project, and it's not one of the above edge cases, // then no confirmation is necessary. Otherwise, confirmation is necessary. @@ -221,7 +230,12 @@ impl AgentTool for EditFileTool { event_stream: ToolCallEventStream, cx: &mut App, ) -> Task> { - let project = self.thread.read(cx).project().clone(); + let Ok(project) = self + .thread + .read_with(cx, |thread, _cx| thread.project().clone()) + else { + return Task::ready(Err(anyhow!("thread was dropped"))); + }; let project_path = match resolve_path(&input, project.clone(), cx) { Ok(path) => path, Err(err) => return Task::ready(Err(anyhow!(err))), @@ -237,23 +251,17 @@ impl AgentTool for EditFileTool { }); } - let Some(request) = self.thread.update(cx, |thread, cx| { - thread - .build_completion_request(CompletionIntent::ToolResults, cx) - .ok() - }) else { - return Task::ready(Err(anyhow!("Failed to build completion request"))); - }; - let thread = self.thread.read(cx); - let Some(model) = thread.model().cloned() else { - return Task::ready(Err(anyhow!("No language model configured"))); - }; - let action_log = thread.action_log().clone(); - let authorize = self.authorize(&input, &event_stream, cx); cx.spawn(async move |cx: &mut AsyncApp| { authorize.await?; + let (request, model, action_log) = self.thread.update(cx, |thread, cx| { + let request = thread.build_completion_request(CompletionIntent::ToolResults, cx); + (request, thread.model().cloned(), thread.action_log().clone()) + })?; + let request = request?; + let model = model.context("No language model configured")?; + let edit_format = EditFormat::from_model(model.clone())?; let edit_agent = EditAgent::new( model, @@ -419,7 +427,6 @@ impl AgentTool for EditFileTool { Ok(EditFileToolOutput { input_path: input.path, - project_path: project_path.path.to_path_buf(), new_text: new_text.clone(), old_text, diff: unified_diff, @@ -427,6 +434,25 @@ impl AgentTool for EditFileTool { }) }) } + + fn replay( + &self, + _input: Self::Input, + output: Self::Output, + event_stream: ToolCallEventStream, + cx: &mut App, + ) -> Result<()> { + event_stream.update_diff(cx.new(|cx| { + Diff::finalized( + output.input_path, + Some(output.old_text.to_string()), + output.new_text, + self.language_registry.clone(), + cx, + ) + })); + Ok(()) + } } /// Validate that the file path is valid, meaning: @@ -471,7 +497,7 @@ fn resolve_path( let parent_entry = parent_project_path .as_ref() - .and_then(|path| project.entry_for_path(&path, cx)) + .and_then(|path| project.entry_for_path(path, cx)) .context("Can't create file: parent directory doesn't exist")?; anyhow::ensure!( @@ -503,9 +529,9 @@ mod tests { use fs::Fs; use gpui::{TestAppContext, UpdateGlobal}; use language_model::fake_provider::FakeLanguageModel; + use prompt_store::ProjectContext; use serde_json::json; use settings::SettingsStore; - use std::rc::Rc; use util::path; #[gpui::test] @@ -515,6 +541,7 @@ mod tests { let fs = project::FakeFs::new(cx.executor()); fs.insert_tree("/root", json!({})).await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let action_log = cx.new(|_| ActionLog::new(project.clone())); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); @@ -522,7 +549,7 @@ mod tests { let thread = cx.new(|cx| { Thread::new( project, - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry, action_log, Templates::new(), @@ -537,7 +564,11 @@ mod tests { path: "root/nonexistent_file.txt".into(), mode: EditFileMode::Edit, }; - Arc::new(EditFileTool { thread }).run(input, ToolCallEventStream::test().0, cx) + Arc::new(EditFileTool::new(thread.downgrade(), language_registry)).run( + input, + ToolCallEventStream::test().0, + cx, + ) }) .await; assert_eq!( @@ -624,8 +655,7 @@ mod tests { mode: mode.clone(), }; - let result = cx.update(|cx| resolve_path(&input, project, cx)); - result + cx.update(|cx| resolve_path(&input, project, cx)) } fn assert_resolved_path_eq(path: anyhow::Result, expected: &str) { @@ -719,7 +749,7 @@ mod tests { let thread = cx.new(|cx| { Thread::new( project, - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry, action_log.clone(), Templates::new(), @@ -750,9 +780,10 @@ mod tests { path: "root/src/main.rs".into(), mode: EditFileMode::Overwrite, }; - Arc::new(EditFileTool { - thread: thread.clone(), - }) + Arc::new(EditFileTool::new( + thread.downgrade(), + language_registry.clone(), + )) .run(input, ToolCallEventStream::test().0, cx) }); @@ -806,7 +837,11 @@ mod tests { path: "root/src/main.rs".into(), mode: EditFileMode::Overwrite, }; - Arc::new(EditFileTool { thread }).run(input, ToolCallEventStream::test().0, cx) + Arc::new(EditFileTool::new(thread.downgrade(), language_registry)).run( + input, + ToolCallEventStream::test().0, + cx, + ) }); // Stream the unformatted content @@ -850,12 +885,13 @@ mod tests { let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let action_log = cx.new(|_| ActionLog::new(project.clone())); let model = Arc::new(FakeLanguageModel::default()); let thread = cx.new(|cx| { Thread::new( project, - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry, action_log.clone(), Templates::new(), @@ -887,9 +923,10 @@ mod tests { path: "root/src/main.rs".into(), mode: EditFileMode::Overwrite, }; - Arc::new(EditFileTool { - thread: thread.clone(), - }) + Arc::new(EditFileTool::new( + thread.downgrade(), + language_registry.clone(), + )) .run(input, ToolCallEventStream::test().0, cx) }); @@ -938,10 +975,11 @@ mod tests { path: "root/src/main.rs".into(), mode: EditFileMode::Overwrite, }; - Arc::new(EditFileTool { - thread: thread.clone(), - }) - .run(input, ToolCallEventStream::test().0, cx) + Arc::new(EditFileTool::new(thread.downgrade(), language_registry)).run( + input, + ToolCallEventStream::test().0, + cx, + ) }); // Stream the content with trailing whitespace @@ -976,12 +1014,13 @@ mod tests { let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let action_log = cx.new(|_| ActionLog::new(project.clone())); let model = Arc::new(FakeLanguageModel::default()); let thread = cx.new(|cx| { Thread::new( project, - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry, action_log.clone(), Templates::new(), @@ -989,7 +1028,7 @@ mod tests { cx, ) }); - let tool = Arc::new(EditFileTool { thread }); + let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry)); fs.insert_tree("/root", json!({})).await; // Test 1: Path with .zed component should require confirmation @@ -1111,6 +1150,7 @@ mod tests { let fs = project::FakeFs::new(cx.executor()); fs.insert_tree("/project", json!({})).await; let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); let action_log = cx.new(|_| ActionLog::new(project.clone())); @@ -1118,7 +1158,7 @@ mod tests { let thread = cx.new(|cx| { Thread::new( project, - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry, action_log.clone(), Templates::new(), @@ -1126,7 +1166,7 @@ mod tests { cx, ) }); - let tool = Arc::new(EditFileTool { thread }); + let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry)); // Test global config paths - these should require confirmation if they exist and are outside the project let test_cases = vec![ @@ -1220,7 +1260,7 @@ mod tests { cx, ) .await; - + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let action_log = cx.new(|_| ActionLog::new(project.clone())); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); @@ -1228,7 +1268,7 @@ mod tests { let thread = cx.new(|cx| { Thread::new( project.clone(), - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry.clone(), action_log.clone(), Templates::new(), @@ -1236,7 +1276,7 @@ mod tests { cx, ) }); - let tool = Arc::new(EditFileTool { thread }); + let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry)); // Test files in different worktrees let test_cases = vec![ @@ -1302,6 +1342,7 @@ mod tests { ) .await; let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let action_log = cx.new(|_| ActionLog::new(project.clone())); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); @@ -1309,7 +1350,7 @@ mod tests { let thread = cx.new(|cx| { Thread::new( project.clone(), - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry.clone(), action_log.clone(), Templates::new(), @@ -1317,7 +1358,7 @@ mod tests { cx, ) }); - let tool = Arc::new(EditFileTool { thread }); + let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry)); // Test edge cases let test_cases = vec![ @@ -1386,6 +1427,7 @@ mod tests { ) .await; let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let action_log = cx.new(|_| ActionLog::new(project.clone())); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); @@ -1393,7 +1435,7 @@ mod tests { let thread = cx.new(|cx| { Thread::new( project.clone(), - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry.clone(), action_log.clone(), Templates::new(), @@ -1401,7 +1443,7 @@ mod tests { cx, ) }); - let tool = Arc::new(EditFileTool { thread }); + let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry)); // Test different EditFileMode values let modes = vec![ @@ -1467,6 +1509,7 @@ mod tests { init_test(cx); let fs = project::FakeFs::new(cx.executor()); let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let action_log = cx.new(|_| ActionLog::new(project.clone())); let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx)); @@ -1474,7 +1517,7 @@ mod tests { let thread = cx.new(|cx| { Thread::new( project.clone(), - Rc::default(), + cx.new(|_cx| ProjectContext::default()), context_server_registry, action_log.clone(), Templates::new(), @@ -1482,7 +1525,7 @@ mod tests { cx, ) }); - let tool = Arc::new(EditFileTool { thread }); + let tool = Arc::new(EditFileTool::new(thread.downgrade(), language_registry)); assert_eq!( tool.initial_title(Err(json!({ diff --git a/crates/agent2/src/tools/grep_tool.rs b/crates/agent2/src/tools/grep_tool.rs index e5d92b3c1d..6d7c05d211 100644 --- a/crates/agent2/src/tools/grep_tool.rs +++ b/crates/agent2/src/tools/grep_tool.rs @@ -179,15 +179,14 @@ impl AgentTool for GrepTool { // Check if this file should be excluded based on its worktree settings if let Ok(Some(project_path)) = project.read_with(cx, |project, cx| { project.find_project_path(&path, cx) - }) { - if cx.update(|cx| { + }) + && cx.update(|cx| { let worktree_settings = WorktreeSettings::get(Some((&project_path).into()), cx); worktree_settings.is_path_excluded(&project_path.path) || worktree_settings.is_path_private(&project_path.path) }).unwrap_or(false) { continue; } - } while *parse_status.borrow() != ParseStatus::Idle { parse_status.changed().await?; @@ -275,12 +274,11 @@ impl AgentTool for GrepTool { output.extend(snapshot.text_for_range(range)); output.push_str("\n```\n"); - if let Some(ancestor_range) = ancestor_range { - if end_row < ancestor_range.end.row { + if let Some(ancestor_range) = ancestor_range + && end_row < ancestor_range.end.row { let remaining_lines = ancestor_range.end.row - end_row; writeln!(output, "\n{} lines remaining in ancestor node. Read the file to see all.", remaining_lines)?; } - } matches_found += 1; } diff --git a/crates/agent2/src/tools/read_file_tool.rs b/crates/agent2/src/tools/read_file_tool.rs index f21643cbbb..f37dff4f47 100644 --- a/crates/agent2/src/tools/read_file_tool.rs +++ b/crates/agent2/src/tools/read_file_tool.rs @@ -175,7 +175,7 @@ impl AgentTool for ReadFileTool { buffer .file() .as_ref() - .map_or(true, |file| !file.disk_state().exists()) + .is_none_or(|file| !file.disk_state().exists()) })? { anyhow::bail!("{file_path} not found"); } diff --git a/crates/agent2/src/tools/terminal_tool.rs b/crates/agent2/src/tools/terminal_tool.rs index ecb855ac34..17e671fba3 100644 --- a/crates/agent2/src/tools/terminal_tool.rs +++ b/crates/agent2/src/tools/terminal_tool.rs @@ -47,12 +47,9 @@ impl TerminalTool { } if which::which("bash").is_ok() { - log::info!("agent selected bash for terminal tool"); "bash".into() } else { - let shell = get_system_shell(); - log::info!("agent selected {shell} for terminal tool"); - shell + get_system_shell() } }); Self { @@ -80,7 +77,7 @@ impl AgentTool for TerminalTool { let first_line = lines.next().unwrap_or_default(); let remaining_line_count = lines.count(); match remaining_line_count { - 0 => MarkdownInlineCode(&first_line).to_string().into(), + 0 => MarkdownInlineCode(first_line).to_string().into(), 1 => MarkdownInlineCode(&format!( "{} - {} more line", first_line, remaining_line_count @@ -271,7 +268,7 @@ fn working_dir( let project = project.read(cx); let cd = &input.cd; - if cd == "." || cd == "" { + if cd == "." || cd.is_empty() { // Accept "." or "" as meaning "the one worktree" if we only have one worktree. let mut worktrees = project.worktrees(cx); @@ -296,10 +293,8 @@ fn working_dir( { return Ok(Some(input_path.into())); } - } else { - if let Some(worktree) = project.worktree_for_root_name(cd, cx) { - return Ok(Some(worktree.read(cx).abs_path().to_path_buf())); - } + } else if let Some(worktree) = project.worktree_for_root_name(cd, cx) { + return Ok(Some(worktree.read(cx).abs_path().to_path_buf())); } anyhow::bail!("`cd` directory {cd:?} was not in any of the project's worktrees."); @@ -319,7 +314,7 @@ mod tests { use theme::ThemeSettings; use util::test::TempTree; - use crate::AgentResponseEvent; + use crate::ThreadEvent; use super::*; @@ -396,7 +391,7 @@ mod tests { }); cx.run_until_parked(); let event = stream_rx.try_next(); - if let Ok(Some(Ok(AgentResponseEvent::ToolCallAuthorization(auth)))) = event { + if let Ok(Some(Ok(ThreadEvent::ToolCallAuthorization(auth)))) = event { auth.response.send(auth.options[0].id.clone()).unwrap(); } diff --git a/crates/agent2/src/tools/web_search_tool.rs b/crates/agent2/src/tools/web_search_tool.rs index c1c0970742..d71a128bfe 100644 --- a/crates/agent2/src/tools/web_search_tool.rs +++ b/crates/agent2/src/tools/web_search_tool.rs @@ -80,33 +80,48 @@ impl AgentTool for WebSearchTool { } }; - let result_text = if response.results.len() == 1 { - "1 result".to_string() - } else { - format!("{} results", response.results.len()) - }; - event_stream.update_fields(acp::ToolCallUpdateFields { - title: Some(format!("Searched the web: {result_text}")), - content: Some( - response - .results - .iter() - .map(|result| acp::ToolCallContent::Content { - content: acp::ContentBlock::ResourceLink(acp::ResourceLink { - name: result.title.clone(), - uri: result.url.clone(), - title: Some(result.title.clone()), - description: Some(result.text.clone()), - mime_type: None, - annotations: None, - size: None, - }), - }) - .collect(), - ), - ..Default::default() - }); + emit_update(&response, &event_stream); Ok(WebSearchToolOutput(response)) }) } + + fn replay( + &self, + _input: Self::Input, + output: Self::Output, + event_stream: ToolCallEventStream, + _cx: &mut App, + ) -> Result<()> { + emit_update(&output.0, &event_stream); + Ok(()) + } +} + +fn emit_update(response: &WebSearchResponse, event_stream: &ToolCallEventStream) { + let result_text = if response.results.len() == 1 { + "1 result".to_string() + } else { + format!("{} results", response.results.len()) + }; + event_stream.update_fields(acp::ToolCallUpdateFields { + title: Some(format!("Searched the web: {result_text}")), + content: Some( + response + .results + .iter() + .map(|result| acp::ToolCallContent::Content { + content: acp::ContentBlock::ResourceLink(acp::ResourceLink { + name: result.title.clone(), + uri: result.url.clone(), + title: Some(result.title.clone()), + description: Some(result.text.clone()), + mime_type: None, + annotations: None, + size: None, + }), + }) + .collect(), + ), + ..Default::default() + }); } diff --git a/crates/agent_servers/Cargo.toml b/crates/agent_servers/Cargo.toml index 81c97c8aa6..b654486cb6 100644 --- a/crates/agent_servers/Cargo.toml +++ b/crates/agent_servers/Cargo.toml @@ -18,7 +18,9 @@ doctest = false [dependencies] acp_thread.workspace = true +action_log.workspace = true agent-client-protocol.workspace = true +agent_settings.workspace = true agentic-coding-protocol.workspace = true anyhow.workspace = true collections.workspace = true @@ -27,11 +29,15 @@ futures.workspace = true gpui.workspace = true indoc.workspace = true itertools.workspace = true +language.workspace = true +language_model.workspace = true +language_models.workspace = true log.workspace = true paths.workspace = true project.workspace = true rand.workspace = true schemars.workspace = true +semver.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index 00e3e3df50..1cfb1fcabf 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -19,14 +19,14 @@ pub async fn connect( root_dir: &Path, cx: &mut AsyncApp, ) -> Result> { - let conn = v1::AcpConnection::stdio(server_name, command.clone(), &root_dir, cx).await; + let conn = v1::AcpConnection::stdio(server_name, command.clone(), root_dir, cx).await; match conn { Ok(conn) => Ok(Rc::new(conn) as _), Err(err) if err.is::() => { // Consider re-using initialize response and subprocess when adding another version here let conn: Rc = - Rc::new(v0::AcpConnection::stdio(server_name, command, &root_dir, cx).await?); + Rc::new(v0::AcpConnection::stdio(server_name, command, root_dir, cx).await?); Ok(conn) } Err(err) => Err(err), diff --git a/crates/agent_servers/src/acp/v0.rs b/crates/agent_servers/src/acp/v0.rs index 74647f7313..30643dd005 100644 --- a/crates/agent_servers/src/acp/v0.rs +++ b/crates/agent_servers/src/acp/v0.rs @@ -1,4 +1,5 @@ // Translates old acp agents into the new schema +use action_log::ActionLog; use agent_client_protocol as acp; use agentic_coding_protocol::{self as acp_old, AgentRequest as _}; use anyhow::{Context as _, Result, anyhow}; @@ -148,7 +149,7 @@ impl acp_old::Client for OldAcpClientDelegate { Ok(acp_old::RequestToolCallConfirmationResponse { id: acp_old::ToolCallId(old_acp_id), - outcome: outcome, + outcome, }) } @@ -265,7 +266,7 @@ impl acp_old::Client for OldAcpClientDelegate { fn into_new_tool_call(id: acp::ToolCallId, request: acp_old::PushToolCallParams) -> acp::ToolCall { acp::ToolCall { - id: id, + id, title: request.label, kind: acp_kind_from_old_icon(request.icon), status: acp::ToolCallStatus::InProgress, @@ -437,13 +438,14 @@ impl AgentConnection for AcpConnection { let result = acp_old::InitializeParams::response_from_any(result)?; if !result.is_authenticated { - anyhow::bail!(AuthRequired) + anyhow::bail!(AuthRequired::new()) } cx.update(|cx| { let thread = cx.new(|cx| { let session_id = acp::SessionId("acp-old-no-id".into()); - AcpThread::new(self.name, self.clone(), project, session_id, cx) + let action_log = cx.new(|_| ActionLog::new(project.clone())); + AcpThread::new(self.name, self.clone(), project, action_log, session_id) }); current_thread.replace(thread.downgrade()); thread diff --git a/crates/agent_servers/src/acp/v1.rs b/crates/agent_servers/src/acp/v1.rs index b77b5ef36d..e0e92f29ba 100644 --- a/crates/agent_servers/src/acp/v1.rs +++ b/crates/agent_servers/src/acp/v1.rs @@ -1,3 +1,4 @@ +use action_log::ActionLog; use agent_client_protocol::{self as acp, Agent as _}; use anyhow::anyhow; use collections::HashMap; @@ -13,7 +14,7 @@ use anyhow::{Context as _, Result}; use gpui::{App, AppContext as _, AsyncApp, Entity, Task, WeakEntity}; use crate::{AgentServerCommand, acp::UnsupportedVersion}; -use acp_thread::{AcpThread, AgentConnection, AuthRequired}; +use acp_thread::{AcpThread, AgentConnection, AuthRequired, LoadError}; pub struct AcpConnection { server_name: &'static str, @@ -86,7 +87,9 @@ impl AcpConnection { for session in sessions.borrow().values() { session .thread - .update(cx, |thread, cx| thread.emit_server_exited(status, cx)) + .update(cx, |thread, cx| { + thread.emit_load_error(LoadError::Exited { status }, cx) + }) .ok(); } @@ -140,21 +143,27 @@ impl AgentConnection for AcpConnection { .await .map_err(|err| { if err.code == acp::ErrorCode::AUTH_REQUIRED.code { - anyhow!(AuthRequired) + let mut error = AuthRequired::new(); + + if err.message != acp::ErrorCode::AUTH_REQUIRED.message { + error = error.with_description(err.message); + } + + anyhow!(error) } else { anyhow!(err) } })?; let session_id = response.session_id; - - let thread = cx.new(|cx| { + let action_log = cx.new(|_| ActionLog::new(project.clone()))?; + let thread = cx.new(|_cx| { AcpThread::new( self.server_name, self.clone(), project, + action_log, session_id.clone(), - cx, ) })?; diff --git a/crates/agent_servers/src/agent_servers.rs b/crates/agent_servers/src/agent_servers.rs index b3b8a33170..cebf82cddb 100644 --- a/crates/agent_servers/src/agent_servers.rs +++ b/crates/agent_servers/src/agent_servers.rs @@ -18,6 +18,7 @@ use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::{ + any::Any, path::{Path, PathBuf}, rc::Rc, sync::Arc, @@ -40,6 +41,14 @@ pub trait AgentServer: Send { project: &Entity, cx: &mut App, ) -> Task>>; + + fn into_any(self: Rc) -> Rc; +} + +impl dyn AgentServer { + pub fn downcast(self: Rc) -> Option> { + self.into_any().downcast().ok() + } } impl std::fmt::Debug for AgentServerCommand { @@ -95,7 +104,7 @@ impl AgentServerCommand { cx: &mut AsyncApp, ) -> Option { if let Some(agent_settings) = settings { - return Some(Self { + Some(Self { path: agent_settings.command.path, args: agent_settings .command @@ -104,7 +113,7 @@ impl AgentServerCommand { .chain(extra_args.iter().map(|arg| arg.to_string())) .collect(), env: agent_settings.command.env, - }); + }) } else { match find_bin_in_path(path_bin_name, project, cx).await { Some(path) => Some(Self { diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs index d15cc1dd89..3008edebeb 100644 --- a/crates/agent_servers/src/claude.rs +++ b/crates/agent_servers/src/claude.rs @@ -1,16 +1,23 @@ +mod edit_tool; mod mcp_server; +mod permission_tool; +mod read_tool; pub mod tools; +mod write_tool; +use action_log::ActionLog; use collections::HashMap; use context_server::listener::McpServerTool; +use language_models::provider::anthropic::AnthropicLanguageModelProvider; use project::Project; use settings::SettingsStore; use smol::process::Child; use std::any::Any; use std::cell::RefCell; use std::fmt::Display; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::rc::Rc; +use util::command::new_smol_command; use uuid::Uuid; use agent_client_protocol as acp; @@ -30,7 +37,7 @@ use util::{ResultExt, debug_panic}; use crate::claude::mcp_server::{ClaudeZedMcpServer, McpConfig}; use crate::claude::tools::ClaudeTool; use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings}; -use acp_thread::{AcpThread, AgentConnection}; +use acp_thread::{AcpThread, AgentConnection, AuthRequired, LoadError, MentionUri}; #[derive(Clone)] pub struct ClaudeCode; @@ -64,6 +71,10 @@ impl AgentServer for ClaudeCode { Task::ready(Ok(Rc::new(connection) as _)) } + + fn into_any(self: Rc) -> Rc { + self + } } struct ClaudeAgentConnection { @@ -79,8 +90,43 @@ impl AgentConnection for ClaudeAgentConnection { ) -> Task>> { let cwd = cwd.to_owned(); cx.spawn(async move |cx| { + let settings = cx.read_global(|settings: &SettingsStore, _| { + settings.get::(None).claude.clone() + })?; + + let Some(command) = AgentServerCommand::resolve( + "claude", + &[], + Some(&util::paths::home_dir().join(".claude/local/claude")), + settings, + &project, + cx, + ) + .await + else { + return Err(LoadError::NotInstalled { + error_message: "Failed to find Claude Code binary".into(), + install_message: "Install Claude Code".into(), + install_command: "npm install -g @anthropic-ai/claude-code@latest".into(), + }.into()); + }; + + let api_key = + cx.update(AnthropicLanguageModelProvider::api_key)? + .await + .map_err(|err| { + if err.is::() { + anyhow!(AuthRequired::new().with_language_model_provider( + language_model::ANTHROPIC_PROVIDER_ID + )) + } else { + anyhow!(err) + } + })?; + let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid()); - let permission_mcp_server = ClaudeZedMcpServer::new(thread_rx.clone(), cx).await?; + let fs = project.read_with(cx, |project, _cx| project.fs().clone())?; + let permission_mcp_server = ClaudeZedMcpServer::new(thread_rx.clone(), fs, cx).await?; let mut mcp_servers = HashMap::default(); mcp_servers.insert( @@ -98,23 +144,6 @@ impl AgentConnection for ClaudeAgentConnection { .await?; mcp_config_file.flush().await?; - let settings = cx.read_global(|settings: &SettingsStore, _| { - settings.get::(None).claude.clone() - })?; - - let Some(command) = AgentServerCommand::resolve( - "claude", - &[], - Some(&util::paths::home_dir().join(".claude/local/claude")), - settings, - &project, - cx, - ) - .await - else { - anyhow::bail!("Failed to find claude binary"); - }; - let (incoming_message_tx, mut incoming_message_rx) = mpsc::unbounded(); let (outgoing_tx, outgoing_rx) = mpsc::unbounded(); @@ -126,6 +155,7 @@ impl AgentConnection for ClaudeAgentConnection { &command, ClaudeSessionMode::Start, session_id.clone(), + api_key, &mcp_config_path, &cwd, )?; @@ -183,20 +213,50 @@ impl AgentConnection for ClaudeAgentConnection { .await } - if let Some(status) = child.status().await.log_err() { - if let Some(thread) = thread_rx.recv().await.ok() { - thread - .update(cx, |thread, cx| { - thread.emit_server_exited(status, cx); - }) - .ok(); - } + if let Some(status) = child.status().await.log_err() + && let Some(thread) = thread_rx.recv().await.ok() + { + let version = claude_version(command.path.clone(), cx).await.log_err(); + let help = claude_help(command.path.clone(), cx).await.log_err(); + thread + .update(cx, |thread, cx| { + let error = if let Some(version) = version + && let Some(help) = help + && (!help.contains("--input-format") + || !help.contains("--session-id")) + { + LoadError::Unsupported { + error_message: format!( + "Your installed version of Claude Code ({}, version {}) does not have required features for use with Zed.", + command.path.to_string_lossy(), + version, + ) + .into(), + upgrade_message: "Upgrade Claude Code to latest".into(), + upgrade_command: format!( + "{} update", + command.path.to_string_lossy() + ), + } + } else { + LoadError::Exited { status } + }; + thread.emit_load_error(error, cx); + }) + .ok(); } } }); - let thread = cx.new(|cx| { - AcpThread::new("Claude Code", self.clone(), project, session_id.clone(), cx) + let action_log = cx.new(|_| ActionLog::new(project.clone()))?; + let thread = cx.new(|_cx| { + AcpThread::new( + "Claude Code", + self.clone(), + project, + action_log, + session_id.clone(), + ) })?; thread_tx.send(thread.downgrade())?; @@ -239,27 +299,12 @@ impl AgentConnection for ClaudeAgentConnection { let (end_tx, end_rx) = oneshot::channel(); session.turn_state.replace(TurnState::InProgress { end_tx }); - let mut content = String::new(); - for chunk in params.prompt { - match chunk { - acp::ContentBlock::Text(text_content) => { - content.push_str(&text_content.text); - } - acp::ContentBlock::ResourceLink(resource_link) => { - content.push_str(&format!("@{}", resource_link.uri)); - } - acp::ContentBlock::Audio(_) - | acp::ContentBlock::Image(_) - | acp::ContentBlock::Resource(_) => { - // TODO - } - } - } + let content = acp_content_to_claude(params.prompt); if let Err(err) = session.outgoing_tx.unbounded_send(SdkMessage::User { message: Message { role: Role::User, - content: Content::UntaggedText(content), + content: Content::Chunks(content), id: None, model: None, stop_reason: None, @@ -276,7 +321,7 @@ impl AgentConnection for ClaudeAgentConnection { fn cancel(&self, session_id: &acp::SessionId, _cx: &mut App) { let sessions = self.sessions.borrow(); - let Some(session) = sessions.get(&session_id) else { + let Some(session) = sessions.get(session_id) else { log::warn!("Attempted to cancel nonexistent session {}", session_id); return; }; @@ -320,6 +365,7 @@ fn spawn_claude( command: &AgentServerCommand, mode: ClaudeSessionMode, session_id: acp::SessionId, + api_key: language_models::provider::anthropic::ApiKey, mcp_config_path: &Path, root_dir: &Path, ) -> Result { @@ -337,24 +383,24 @@ fn spawn_claude( &format!( "mcp__{}__{}", mcp_server::SERVER_NAME, - mcp_server::PermissionTool::NAME, + permission_tool::PermissionTool::NAME, ), "--allowedTools", &format!( - "mcp__{}__{},mcp__{}__{}", + "mcp__{}__{}", mcp_server::SERVER_NAME, - mcp_server::EditTool::NAME, - mcp_server::SERVER_NAME, - mcp_server::ReadTool::NAME + read_tool::ReadTool::NAME ), "--disallowedTools", - "Read,Edit", + "Read,Write,Edit,MultiEdit", ]) .args(match mode { ClaudeSessionMode::Start => ["--session-id".to_string(), session_id.to_string()], ClaudeSessionMode::Resume => ["--resume".to_string(), session_id.to_string()], }) .args(command.args.iter().map(|arg| arg.as_str())) + .envs(command.env.iter().flatten()) + .env("ANTHROPIC_API_KEY", api_key.key) .current_dir(root_dir) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) @@ -365,6 +411,27 @@ fn spawn_claude( Ok(child) } +fn claude_version(path: PathBuf, cx: &mut AsyncApp) -> Task> { + cx.background_spawn(async move { + let output = new_smol_command(path).arg("--version").output().await?; + let output = String::from_utf8(output.stdout)?; + let version = output + .trim() + .strip_suffix(" (Claude Code)") + .context("parsing Claude version")?; + let version = semver::Version::parse(version)?; + anyhow::Ok(version) + }) +} + +fn claude_help(path: PathBuf, cx: &mut AsyncApp) -> Task> { + cx.background_spawn(async move { + let output = new_smol_command(path).arg("--help").output().await?; + let output = String::from_utf8(output.stdout)?; + anyhow::Ok(output) + }) +} + struct ClaudeAgentSession { outgoing_tx: UnboundedSender, turn_state: Rc>, @@ -454,9 +521,16 @@ impl ClaudeAgentSession { let content = content.to_string(); thread .update(cx, |thread, cx| { + let id = acp::ToolCallId(tool_use_id.into()); + let set_new_content = !content.is_empty() + && thread.tool_call(&id).is_none_or(|(_, tool_call)| { + // preserve rich diff if we have one + tool_call.diffs().next().is_none() + }); + thread.update_tool_call( acp::ToolCallUpdate { - id: acp::ToolCallId(tool_use_id.into()), + id, fields: acp::ToolCallUpdateFields { status: if turn_state.borrow().is_canceled() { // Do not set to completed if turn was canceled @@ -464,7 +538,7 @@ impl ClaudeAgentSession { } else { Some(acp::ToolCallStatus::Completed) }, - content: (!content.is_empty()) + content: set_new_content .then(|| vec![content.into()]), ..Default::default() }, @@ -482,10 +556,17 @@ impl ClaudeAgentSession { chunk ); } + ContentChunk::Image { source } => { + if !turn_state.borrow().is_canceled() { + thread + .update(cx, |thread, cx| { + thread.push_user_content_block(None, source.into(), cx) + }) + .log_err(); + } + } - ContentChunk::Image - | ContentChunk::Document - | ContentChunk::WebSearchToolResult => { + ContentChunk::Document | ContentChunk::WebSearchToolResult => { thread .update(cx, |thread, cx| { thread.push_assistant_content_block( @@ -571,7 +652,14 @@ impl ClaudeAgentSession { "Should not get tool results with role: assistant. should we handle this?" ); } - ContentChunk::Image | ContentChunk::Document => { + ContentChunk::Image { source } => { + thread + .update(cx, |thread, cx| { + thread.push_assistant_content_block(source.into(), false, cx) + }) + .log_err(); + } + ContentChunk::Document => { thread .update(cx, |thread, cx| { thread.push_assistant_content_block( @@ -737,14 +825,44 @@ enum ContentChunk { thinking: String, }, RedactedThinking, + Image { + source: ImageSource, + }, // TODO - Image, Document, WebSearchToolResult, #[serde(untagged)] UntaggedText(String), } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum ImageSource { + Base64 { data: String, media_type: String }, + Url { url: String }, +} + +impl Into for ImageSource { + fn into(self) -> acp::ContentBlock { + match self { + ImageSource::Base64 { data, media_type } => { + acp::ContentBlock::Image(acp::ImageContent { + annotations: None, + data, + mime_type: media_type, + uri: None, + }) + } + ImageSource::Url { url } => acp::ContentBlock::Image(acp::ImageContent { + annotations: None, + data: "".to_string(), + mime_type: "".to_string(), + uri: Some(url), + }), + } + } +} + impl Display for ContentChunk { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -753,7 +871,7 @@ impl Display for ContentChunk { ContentChunk::RedactedThinking => write!(f, "Thinking: [REDACTED]"), ContentChunk::UntaggedText(text) => write!(f, "{}", text), ContentChunk::ToolResult { content, .. } => write!(f, "{}", content), - ContentChunk::Image + ContentChunk::Image { .. } | ContentChunk::Document | ContentChunk::ToolUse { .. } | ContentChunk::WebSearchToolResult => { @@ -865,6 +983,75 @@ impl Display for ResultErrorType { } } +fn acp_content_to_claude(prompt: Vec) -> Vec { + let mut content = Vec::with_capacity(prompt.len()); + let mut context = Vec::with_capacity(prompt.len()); + + for chunk in prompt { + match chunk { + acp::ContentBlock::Text(text_content) => { + content.push(ContentChunk::Text { + text: text_content.text, + }); + } + acp::ContentBlock::ResourceLink(resource_link) => { + match MentionUri::parse(&resource_link.uri) { + Ok(uri) => { + content.push(ContentChunk::Text { + text: format!("{}", uri.as_link()), + }); + } + Err(_) => { + content.push(ContentChunk::Text { + text: resource_link.uri, + }); + } + } + } + acp::ContentBlock::Resource(resource) => match resource.resource { + acp::EmbeddedResourceResource::TextResourceContents(resource) => { + match MentionUri::parse(&resource.uri) { + Ok(uri) => { + content.push(ContentChunk::Text { + text: format!("{}", uri.as_link()), + }); + } + Err(_) => { + content.push(ContentChunk::Text { + text: resource.uri.clone(), + }); + } + } + + context.push(ContentChunk::Text { + text: format!( + "\n\n{}\n", + resource.uri, resource.text + ), + }); + } + acp::EmbeddedResourceResource::BlobResourceContents(_) => { + // Unsupported by SDK + } + }, + acp::ContentBlock::Image(acp::ImageContent { + data, mime_type, .. + }) => content.push(ContentChunk::Image { + source: ImageSource::Base64 { + data, + media_type: mime_type, + }, + }), + acp::ContentBlock::Audio(_) => { + // Unsupported by SDK + } + } + } + + content.extend(context); + content +} + fn new_request_id() -> String { use rand::Rng; // In the Claude Code TS SDK they just generate a random 12 character string, @@ -1081,4 +1268,100 @@ pub(crate) mod tests { _ => panic!("Expected ToolResult variant"), } } + + #[test] + fn test_acp_content_to_claude() { + let acp_content = vec![ + acp::ContentBlock::Text(acp::TextContent { + text: "Hello world".to_string(), + annotations: None, + }), + acp::ContentBlock::Image(acp::ImageContent { + data: "base64data".to_string(), + mime_type: "image/png".to_string(), + annotations: None, + uri: None, + }), + acp::ContentBlock::ResourceLink(acp::ResourceLink { + uri: "file:///path/to/example.rs".to_string(), + name: "example.rs".to_string(), + annotations: None, + description: None, + mime_type: None, + size: None, + title: None, + }), + acp::ContentBlock::Resource(acp::EmbeddedResource { + annotations: None, + resource: acp::EmbeddedResourceResource::TextResourceContents( + acp::TextResourceContents { + mime_type: None, + text: "fn main() { println!(\"Hello!\"); }".to_string(), + uri: "file:///path/to/code.rs".to_string(), + }, + ), + }), + acp::ContentBlock::ResourceLink(acp::ResourceLink { + uri: "invalid_uri_format".to_string(), + name: "invalid.txt".to_string(), + annotations: None, + description: None, + mime_type: None, + size: None, + title: None, + }), + ]; + + let claude_content = acp_content_to_claude(acp_content); + + assert_eq!(claude_content.len(), 6); + + match &claude_content[0] { + ContentChunk::Text { text } => assert_eq!(text, "Hello world"), + _ => panic!("Expected Text chunk"), + } + + match &claude_content[1] { + ContentChunk::Image { source } => match source { + ImageSource::Base64 { data, media_type } => { + assert_eq!(data, "base64data"); + assert_eq!(media_type, "image/png"); + } + _ => panic!("Expected Base64 image source"), + }, + _ => panic!("Expected Image chunk"), + } + + match &claude_content[2] { + ContentChunk::Text { text } => { + assert!(text.contains("example.rs")); + assert!(text.contains("file:///path/to/example.rs")); + } + _ => panic!("Expected Text chunk for ResourceLink"), + } + + match &claude_content[3] { + ContentChunk::Text { text } => { + assert!(text.contains("code.rs")); + assert!(text.contains("file:///path/to/code.rs")); + } + _ => panic!("Expected Text chunk for Resource"), + } + + match &claude_content[4] { + ContentChunk::Text { text } => { + assert_eq!(text, "invalid_uri_format"); + } + _ => panic!("Expected Text chunk for invalid URI"), + } + + match &claude_content[5] { + ContentChunk::Text { text } => { + assert!(text.contains("")); + assert!(text.contains("fn main() { println!(\"Hello!\"); }")); + assert!(text.contains("")); + } + _ => panic!("Expected Text chunk for context"), + } + } } diff --git a/crates/agent_servers/src/claude/edit_tool.rs b/crates/agent_servers/src/claude/edit_tool.rs new file mode 100644 index 0000000000..a8d93c3f3d --- /dev/null +++ b/crates/agent_servers/src/claude/edit_tool.rs @@ -0,0 +1,178 @@ +use acp_thread::AcpThread; +use anyhow::Result; +use context_server::{ + listener::{McpServerTool, ToolResponse}, + types::{ToolAnnotations, ToolResponseContent}, +}; +use gpui::{AsyncApp, WeakEntity}; +use language::unified_diff; +use util::markdown::MarkdownCodeBlock; + +use crate::tools::EditToolParams; + +#[derive(Clone)] +pub struct EditTool { + thread_rx: watch::Receiver>, +} + +impl EditTool { + pub fn new(thread_rx: watch::Receiver>) -> Self { + Self { thread_rx } + } +} + +impl McpServerTool for EditTool { + type Input = EditToolParams; + type Output = (); + + const NAME: &'static str = "Edit"; + + fn annotations(&self) -> ToolAnnotations { + ToolAnnotations { + title: Some("Edit file".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + open_world_hint: Some(false), + idempotent_hint: Some(false), + } + } + + async fn run( + &self, + input: Self::Input, + cx: &mut AsyncApp, + ) -> Result> { + let mut thread_rx = self.thread_rx.clone(); + let Some(thread) = thread_rx.recv().await?.upgrade() else { + anyhow::bail!("Thread closed"); + }; + + let content = thread + .update(cx, |thread, cx| { + thread.read_text_file(input.abs_path.clone(), None, None, true, cx) + })? + .await?; + + let (new_content, diff) = cx + .background_executor() + .spawn(async move { + let new_content = content.replace(&input.old_text, &input.new_text); + if new_content == content { + return Err(anyhow::anyhow!("Failed to find `old_text`",)); + } + let diff = unified_diff(&content, &new_content); + + Ok((new_content, diff)) + }) + .await?; + + thread + .update(cx, |thread, cx| { + thread.write_text_file(input.abs_path, new_content, cx) + })? + .await?; + + Ok(ToolResponse { + content: vec![ToolResponseContent::Text { + text: MarkdownCodeBlock { + tag: "diff", + text: diff.as_str().trim_end_matches('\n'), + } + .to_string(), + }], + structured_content: (), + }) + } +} + +#[cfg(test)] +mod tests { + use std::rc::Rc; + + use acp_thread::{AgentConnection, StubAgentConnection}; + use gpui::{Entity, TestAppContext}; + use indoc::indoc; + use project::{FakeFs, Project}; + use serde_json::json; + use settings::SettingsStore; + use util::path; + + use super::*; + + #[gpui::test] + async fn old_text_not_found(cx: &mut TestAppContext) { + let (_thread, tool) = init_test(cx).await; + + let result = tool + .run( + EditToolParams { + abs_path: path!("/root/file.txt").into(), + old_text: "hi".into(), + new_text: "bye".into(), + }, + &mut cx.to_async(), + ) + .await; + + assert_eq!(result.unwrap_err().to_string(), "Failed to find `old_text`"); + } + + #[gpui::test] + async fn found_and_replaced(cx: &mut TestAppContext) { + let (_thread, tool) = init_test(cx).await; + + let result = tool + .run( + EditToolParams { + abs_path: path!("/root/file.txt").into(), + old_text: "hello".into(), + new_text: "hi".into(), + }, + &mut cx.to_async(), + ) + .await; + + assert_eq!( + result.unwrap().content[0].text().unwrap(), + indoc! { + r" + ```diff + @@ -1,1 +1,1 @@ + -hello + +hi + ``` + " + } + ); + } + + async fn init_test(cx: &mut TestAppContext) -> (Entity, EditTool) { + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + language::init(cx); + Project::init_settings(cx); + }); + + let connection = Rc::new(StubAgentConnection::new()); + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/root"), + json!({ + "file.txt": "hello" + }), + ) + .await; + let project = Project::test(fs, [path!("/root").as_ref()], cx).await; + let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid()); + + let thread = cx + .update(|cx| connection.new_thread(project, path!("/test").as_ref(), cx)) + .await + .unwrap(); + + thread_tx.send(thread.downgrade()).unwrap(); + + (thread, EditTool::new(thread_rx)) + } +} diff --git a/crates/agent_servers/src/claude/mcp_server.rs b/crates/agent_servers/src/claude/mcp_server.rs index 22cb2f8f8d..6442c784b5 100644 --- a/crates/agent_servers/src/claude/mcp_server.rs +++ b/crates/agent_servers/src/claude/mcp_server.rs @@ -1,18 +1,22 @@ use std::path::PathBuf; +use std::sync::Arc; -use crate::claude::tools::{ClaudeTool, EditToolParams, ReadToolParams}; +use crate::claude::edit_tool::EditTool; +use crate::claude::permission_tool::PermissionTool; +use crate::claude::read_tool::ReadTool; +use crate::claude::write_tool::WriteTool; use acp_thread::AcpThread; -use agent_client_protocol as acp; -use anyhow::{Context, Result}; +#[cfg(not(test))] +use anyhow::Context as _; +use anyhow::Result; use collections::HashMap; -use context_server::listener::{McpServerTool, ToolResponse}; use context_server::types::{ Implementation, InitializeParams, InitializeResponse, ProtocolVersion, ServerCapabilities, - ToolAnnotations, ToolResponseContent, ToolsCapabilities, requests, + ToolsCapabilities, requests, }; use gpui::{App, AsyncApp, Task, WeakEntity}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use project::Fs; +use serde::Serialize; pub struct ClaudeZedMcpServer { server: context_server::listener::McpServer, @@ -23,20 +27,16 @@ pub const SERVER_NAME: &str = "zed"; impl ClaudeZedMcpServer { pub async fn new( thread_rx: watch::Receiver>, + fs: Arc, cx: &AsyncApp, ) -> Result { let mut mcp_server = context_server::listener::McpServer::new(cx).await?; mcp_server.handle_request::(Self::handle_initialize); - mcp_server.add_tool(PermissionTool { - thread_rx: thread_rx.clone(), - }); - mcp_server.add_tool(ReadTool { - thread_rx: thread_rx.clone(), - }); - mcp_server.add_tool(EditTool { - thread_rx: thread_rx.clone(), - }); + mcp_server.add_tool(PermissionTool::new(fs.clone(), thread_rx.clone())); + mcp_server.add_tool(ReadTool::new(thread_rx.clone())); + mcp_server.add_tool(EditTool::new(thread_rx.clone())); + mcp_server.add_tool(WriteTool::new(thread_rx.clone())); Ok(Self { server: mcp_server }) } @@ -97,206 +97,3 @@ pub struct McpServerConfig { #[serde(skip_serializing_if = "Option::is_none")] pub env: Option>, } - -// Tools - -#[derive(Clone)] -pub struct PermissionTool { - thread_rx: watch::Receiver>, -} - -#[derive(Deserialize, JsonSchema, Debug)] -pub struct PermissionToolParams { - tool_name: String, - input: serde_json::Value, - tool_use_id: Option, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct PermissionToolResponse { - behavior: PermissionToolBehavior, - updated_input: serde_json::Value, -} - -#[derive(Serialize)] -#[serde(rename_all = "snake_case")] -enum PermissionToolBehavior { - Allow, - Deny, -} - -impl McpServerTool for PermissionTool { - type Input = PermissionToolParams; - type Output = (); - - const NAME: &'static str = "Confirmation"; - - fn description(&self) -> &'static str { - "Request permission for tool calls" - } - - async fn run( - &self, - input: Self::Input, - cx: &mut AsyncApp, - ) -> Result> { - let mut thread_rx = self.thread_rx.clone(); - let Some(thread) = thread_rx.recv().await?.upgrade() else { - anyhow::bail!("Thread closed"); - }; - - let claude_tool = ClaudeTool::infer(&input.tool_name, input.input.clone()); - let tool_call_id = acp::ToolCallId(input.tool_use_id.context("Tool ID required")?.into()); - let allow_option_id = acp::PermissionOptionId("allow".into()); - let reject_option_id = acp::PermissionOptionId("reject".into()); - - let chosen_option = thread - .update(cx, |thread, cx| { - thread.request_tool_call_authorization( - claude_tool.as_acp(tool_call_id).into(), - vec![ - acp::PermissionOption { - id: allow_option_id.clone(), - name: "Allow".into(), - kind: acp::PermissionOptionKind::AllowOnce, - }, - acp::PermissionOption { - id: reject_option_id.clone(), - name: "Reject".into(), - kind: acp::PermissionOptionKind::RejectOnce, - }, - ], - cx, - ) - })?? - .await?; - - let response = if chosen_option == allow_option_id { - PermissionToolResponse { - behavior: PermissionToolBehavior::Allow, - updated_input: input.input, - } - } else { - debug_assert_eq!(chosen_option, reject_option_id); - PermissionToolResponse { - behavior: PermissionToolBehavior::Deny, - updated_input: input.input, - } - }; - - Ok(ToolResponse { - content: vec![ToolResponseContent::Text { - text: serde_json::to_string(&response)?, - }], - structured_content: (), - }) - } -} - -#[derive(Clone)] -pub struct ReadTool { - thread_rx: watch::Receiver>, -} - -impl McpServerTool for ReadTool { - type Input = ReadToolParams; - type Output = (); - - const NAME: &'static str = "Read"; - - fn description(&self) -> &'static str { - "Read the contents of a file. In sessions with mcp__zed__Read always use it instead of Read as it contains the most up-to-date contents." - } - - fn annotations(&self) -> ToolAnnotations { - ToolAnnotations { - title: Some("Read file".to_string()), - read_only_hint: Some(true), - destructive_hint: Some(false), - open_world_hint: Some(false), - idempotent_hint: None, - } - } - - async fn run( - &self, - input: Self::Input, - cx: &mut AsyncApp, - ) -> Result> { - let mut thread_rx = self.thread_rx.clone(); - let Some(thread) = thread_rx.recv().await?.upgrade() else { - anyhow::bail!("Thread closed"); - }; - - let content = thread - .update(cx, |thread, cx| { - thread.read_text_file(input.abs_path, input.offset, input.limit, false, cx) - })? - .await?; - - Ok(ToolResponse { - content: vec![ToolResponseContent::Text { text: content }], - structured_content: (), - }) - } -} - -#[derive(Clone)] -pub struct EditTool { - thread_rx: watch::Receiver>, -} - -impl McpServerTool for EditTool { - type Input = EditToolParams; - type Output = (); - - const NAME: &'static str = "Edit"; - - fn description(&self) -> &'static str { - "Edits a file. In sessions with mcp__zed__Edit always use it instead of Edit as it will show the diff to the user better." - } - - fn annotations(&self) -> ToolAnnotations { - ToolAnnotations { - title: Some("Edit file".to_string()), - read_only_hint: Some(false), - destructive_hint: Some(false), - open_world_hint: Some(false), - idempotent_hint: Some(false), - } - } - - async fn run( - &self, - input: Self::Input, - cx: &mut AsyncApp, - ) -> Result> { - let mut thread_rx = self.thread_rx.clone(); - let Some(thread) = thread_rx.recv().await?.upgrade() else { - anyhow::bail!("Thread closed"); - }; - - let content = thread - .update(cx, |thread, cx| { - thread.read_text_file(input.abs_path.clone(), None, None, true, cx) - })? - .await?; - - let new_content = content.replace(&input.old_text, &input.new_text); - if new_content == content { - return Err(anyhow::anyhow!("The old_text was not found in the content")); - } - - thread - .update(cx, |thread, cx| { - thread.write_text_file(input.abs_path, new_content, cx) - })? - .await?; - - Ok(ToolResponse { - content: vec![], - structured_content: (), - }) - } -} diff --git a/crates/agent_servers/src/claude/permission_tool.rs b/crates/agent_servers/src/claude/permission_tool.rs new file mode 100644 index 0000000000..96a24105e8 --- /dev/null +++ b/crates/agent_servers/src/claude/permission_tool.rs @@ -0,0 +1,158 @@ +use std::sync::Arc; + +use acp_thread::AcpThread; +use agent_client_protocol as acp; +use agent_settings::AgentSettings; +use anyhow::{Context as _, Result}; +use context_server::{ + listener::{McpServerTool, ToolResponse}, + types::ToolResponseContent, +}; +use gpui::{AsyncApp, WeakEntity}; +use project::Fs; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::{Settings as _, update_settings_file}; +use util::debug_panic; + +use crate::tools::ClaudeTool; + +#[derive(Clone)] +pub struct PermissionTool { + fs: Arc, + thread_rx: watch::Receiver>, +} + +/// Request permission for tool calls +#[derive(Deserialize, JsonSchema, Debug)] +pub struct PermissionToolParams { + tool_name: String, + input: serde_json::Value, + tool_use_id: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionToolResponse { + behavior: PermissionToolBehavior, + updated_input: serde_json::Value, +} + +#[derive(Serialize)] +#[serde(rename_all = "snake_case")] +enum PermissionToolBehavior { + Allow, + Deny, +} + +impl PermissionTool { + pub fn new(fs: Arc, thread_rx: watch::Receiver>) -> Self { + Self { fs, thread_rx } + } +} + +impl McpServerTool for PermissionTool { + type Input = PermissionToolParams; + type Output = (); + + const NAME: &'static str = "Confirmation"; + + async fn run( + &self, + input: Self::Input, + cx: &mut AsyncApp, + ) -> Result> { + if agent_settings::AgentSettings::try_read_global(cx, |settings| { + settings.always_allow_tool_actions + }) + .unwrap_or(false) + { + let response = PermissionToolResponse { + behavior: PermissionToolBehavior::Allow, + updated_input: input.input, + }; + + return Ok(ToolResponse { + content: vec![ToolResponseContent::Text { + text: serde_json::to_string(&response)?, + }], + structured_content: (), + }); + } + + let mut thread_rx = self.thread_rx.clone(); + let Some(thread) = thread_rx.recv().await?.upgrade() else { + anyhow::bail!("Thread closed"); + }; + + let claude_tool = ClaudeTool::infer(&input.tool_name, input.input.clone()); + let tool_call_id = acp::ToolCallId(input.tool_use_id.context("Tool ID required")?.into()); + + const ALWAYS_ALLOW: &str = "always_allow"; + const ALLOW: &str = "allow"; + const REJECT: &str = "reject"; + + let chosen_option = thread + .update(cx, |thread, cx| { + thread.request_tool_call_authorization( + claude_tool.as_acp(tool_call_id).into(), + vec![ + acp::PermissionOption { + id: acp::PermissionOptionId(ALWAYS_ALLOW.into()), + name: "Always Allow".into(), + kind: acp::PermissionOptionKind::AllowAlways, + }, + acp::PermissionOption { + id: acp::PermissionOptionId(ALLOW.into()), + name: "Allow".into(), + kind: acp::PermissionOptionKind::AllowOnce, + }, + acp::PermissionOption { + id: acp::PermissionOptionId(REJECT.into()), + name: "Reject".into(), + kind: acp::PermissionOptionKind::RejectOnce, + }, + ], + cx, + ) + })?? + .await?; + + let response = match chosen_option.0.as_ref() { + ALWAYS_ALLOW => { + cx.update(|cx| { + update_settings_file::(self.fs.clone(), cx, |settings, _| { + settings.set_always_allow_tool_actions(true); + }); + })?; + + PermissionToolResponse { + behavior: PermissionToolBehavior::Allow, + updated_input: input.input, + } + } + ALLOW => PermissionToolResponse { + behavior: PermissionToolBehavior::Allow, + updated_input: input.input, + }, + REJECT => PermissionToolResponse { + behavior: PermissionToolBehavior::Deny, + updated_input: input.input, + }, + opt => { + debug_panic!("Unexpected option: {}", opt); + PermissionToolResponse { + behavior: PermissionToolBehavior::Deny, + updated_input: input.input, + } + } + }; + + Ok(ToolResponse { + content: vec![ToolResponseContent::Text { + text: serde_json::to_string(&response)?, + }], + structured_content: (), + }) + } +} diff --git a/crates/agent_servers/src/claude/read_tool.rs b/crates/agent_servers/src/claude/read_tool.rs new file mode 100644 index 0000000000..cbe25876b3 --- /dev/null +++ b/crates/agent_servers/src/claude/read_tool.rs @@ -0,0 +1,59 @@ +use acp_thread::AcpThread; +use anyhow::Result; +use context_server::{ + listener::{McpServerTool, ToolResponse}, + types::{ToolAnnotations, ToolResponseContent}, +}; +use gpui::{AsyncApp, WeakEntity}; + +use crate::tools::ReadToolParams; + +#[derive(Clone)] +pub struct ReadTool { + thread_rx: watch::Receiver>, +} + +impl ReadTool { + pub fn new(thread_rx: watch::Receiver>) -> Self { + Self { thread_rx } + } +} + +impl McpServerTool for ReadTool { + type Input = ReadToolParams; + type Output = (); + + const NAME: &'static str = "Read"; + + fn annotations(&self) -> ToolAnnotations { + ToolAnnotations { + title: Some("Read file".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + open_world_hint: Some(false), + idempotent_hint: None, + } + } + + async fn run( + &self, + input: Self::Input, + cx: &mut AsyncApp, + ) -> Result> { + let mut thread_rx = self.thread_rx.clone(); + let Some(thread) = thread_rx.recv().await?.upgrade() else { + anyhow::bail!("Thread closed"); + }; + + let content = thread + .update(cx, |thread, cx| { + thread.read_text_file(input.abs_path, input.offset, input.limit, false, cx) + })? + .await?; + + Ok(ToolResponse { + content: vec![ToolResponseContent::Text { text: content }], + structured_content: (), + }) + } +} diff --git a/crates/agent_servers/src/claude/tools.rs b/crates/agent_servers/src/claude/tools.rs index 7ca150c0bd..3be10ed94c 100644 --- a/crates/agent_servers/src/claude/tools.rs +++ b/crates/agent_servers/src/claude/tools.rs @@ -34,6 +34,7 @@ impl ClaudeTool { // Known tools "mcp__zed__Read" => Self::ReadFile(serde_json::from_value(input).log_err()), "mcp__zed__Edit" => Self::Edit(serde_json::from_value(input).log_err()), + "mcp__zed__Write" => Self::Write(serde_json::from_value(input).log_err()), "MultiEdit" => Self::MultiEdit(serde_json::from_value(input).log_err()), "Write" => Self::Write(serde_json::from_value(input).log_err()), "LS" => Self::Ls(serde_json::from_value(input).log_err()), @@ -93,7 +94,7 @@ impl ClaudeTool { } Self::MultiEdit(None) => "Multi Edit".into(), Self::Write(Some(params)) => { - format!("Write {}", params.file_path.display()) + format!("Write {}", params.abs_path.display()) } Self::Write(None) => "Write".into(), Self::Glob(Some(params)) => { @@ -153,7 +154,7 @@ impl ClaudeTool { }], Self::Write(Some(params)) => vec![acp::ToolCallContent::Diff { diff: acp::Diff { - path: params.file_path.clone(), + path: params.abs_path.clone(), old_text: None, new_text: params.content.clone(), }, @@ -229,7 +230,10 @@ impl ClaudeTool { line: None, }] } - Self::Write(Some(WriteToolParams { file_path, .. })) => { + Self::Write(Some(WriteToolParams { + abs_path: file_path, + .. + })) => { vec![acp::ToolCallLocation { path: file_path.clone(), line: None, @@ -302,6 +306,20 @@ impl ClaudeTool { } } +/// Edit a file. +/// +/// In sessions with mcp__zed__Edit always use it instead of Edit as it will +/// allow the user to conveniently review changes. +/// +/// File editing instructions: +/// - The `old_text` param must match existing file content, including indentation. +/// - The `old_text` param must come from the actual file, not an outline. +/// - The `old_text` section must not be empty. +/// - Be minimal with replacements: +/// - For unique lines, include only those lines. +/// - For non-unique lines, include enough context to identify them. +/// - Do not escape quotes, newlines, or other characters. +/// - Only edit the specified file. #[derive(Deserialize, JsonSchema, Debug)] pub struct EditToolParams { /// The absolute path to the file to read. @@ -312,6 +330,11 @@ pub struct EditToolParams { pub new_text: String, } +/// Reads the content of the given file in the project. +/// +/// Never attempt to read a path that hasn't been previously mentioned. +/// +/// In sessions with mcp__zed__Read always use it instead of Read as it contains the most up-to-date contents. #[derive(Deserialize, JsonSchema, Debug)] pub struct ReadToolParams { /// The absolute path to the file to read. @@ -324,11 +347,15 @@ pub struct ReadToolParams { pub limit: Option, } +/// Writes content to the specified file in the project. +/// +/// In sessions with mcp__zed__Write always use it instead of Write as it will +/// allow the user to conveniently review changes. #[derive(Deserialize, JsonSchema, Debug)] pub struct WriteToolParams { - /// Absolute path for new file - pub file_path: PathBuf, - /// File content + /// The absolute path of the file to write. + pub abs_path: PathBuf, + /// The full content to write. pub content: String, } diff --git a/crates/agent_servers/src/claude/write_tool.rs b/crates/agent_servers/src/claude/write_tool.rs new file mode 100644 index 0000000000..39479a9c38 --- /dev/null +++ b/crates/agent_servers/src/claude/write_tool.rs @@ -0,0 +1,59 @@ +use acp_thread::AcpThread; +use anyhow::Result; +use context_server::{ + listener::{McpServerTool, ToolResponse}, + types::ToolAnnotations, +}; +use gpui::{AsyncApp, WeakEntity}; + +use crate::tools::WriteToolParams; + +#[derive(Clone)] +pub struct WriteTool { + thread_rx: watch::Receiver>, +} + +impl WriteTool { + pub fn new(thread_rx: watch::Receiver>) -> Self { + Self { thread_rx } + } +} + +impl McpServerTool for WriteTool { + type Input = WriteToolParams; + type Output = (); + + const NAME: &'static str = "Write"; + + fn annotations(&self) -> ToolAnnotations { + ToolAnnotations { + title: Some("Write file".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + open_world_hint: Some(false), + idempotent_hint: Some(false), + } + } + + async fn run( + &self, + input: Self::Input, + cx: &mut AsyncApp, + ) -> Result> { + let mut thread_rx = self.thread_rx.clone(); + let Some(thread) = thread_rx.recv().await?.upgrade() else { + anyhow::bail!("Thread closed"); + }; + + thread + .update(cx, |thread, cx| { + thread.write_text_file(input.abs_path, input.content, cx) + })? + .await?; + + Ok(ToolResponse { + content: vec![], + structured_content: (), + }) + } +} diff --git a/crates/agent_servers/src/e2e_tests.rs b/crates/agent_servers/src/e2e_tests.rs index 2b32edcd4f..8b2703575d 100644 --- a/crates/agent_servers/src/e2e_tests.rs +++ b/crates/agent_servers/src/e2e_tests.rs @@ -428,12 +428,9 @@ pub async fn new_test_thread( .await .unwrap(); - let thread = cx - .update(|cx| connection.new_thread(project.clone(), current_dir.as_ref(), cx)) + cx.update(|cx| connection.new_thread(project.clone(), current_dir.as_ref(), cx)) .await - .unwrap(); - - thread + .unwrap() } pub async fn run_until_first_tool_call( @@ -471,7 +468,7 @@ pub fn get_zed_path() -> PathBuf { while zed_path .file_name() - .map_or(true, |name| name.to_string_lossy() != "debug") + .is_none_or(|name| name.to_string_lossy() != "debug") { if !zed_path.pop() { panic!("Could not find target directory"); diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs index ad883f6da8..813f8b1fe0 100644 --- a/crates/agent_servers/src/gemini.rs +++ b/crates/agent_servers/src/gemini.rs @@ -1,5 +1,5 @@ -use std::path::Path; use std::rc::Rc; +use std::{any::Any, path::Path}; use crate::{AgentServer, AgentServerCommand}; use acp_thread::{AgentConnection, LoadError}; @@ -26,7 +26,7 @@ impl AgentServer for Gemini { } fn empty_state_message(&self) -> &'static str { - "Ask questions, edit files, run commands.\nBe specific for the best results." + "Ask questions, edit files, run commands" } fn logo(&self) -> ui::IconName { @@ -50,7 +50,11 @@ impl AgentServer for Gemini { let Some(command) = AgentServerCommand::resolve("gemini", &[ACP_ARG], None, settings, &project, cx).await else { - anyhow::bail!("Failed to find gemini binary"); + return Err(LoadError::NotInstalled { + error_message: "Failed to find Gemini CLI binary".into(), + install_message: "Install Gemini CLI".into(), + install_command: "npm install -g @google/gemini-cli@latest".into() + }.into()); }; let result = crate::acp::connect(server_name, command.clone(), &root_dir, cx).await; @@ -75,10 +79,11 @@ impl AgentServer for Gemini { if !supported { return Err(LoadError::Unsupported { error_message: format!( - "Your installed version of Gemini {} doesn't support the Agentic Coding Protocol (ACP).", + "Your installed version of Gemini CLI ({}, version {}) doesn't support the Agentic Coding Protocol (ACP).", + command.path.to_string_lossy(), current_version ).into(), - upgrade_message: "Upgrade Gemini to Latest".into(), + upgrade_message: "Upgrade Gemini CLI to latest".into(), upgrade_command: "npm install -g @google/gemini-cli@latest".into(), }.into()) } @@ -86,6 +91,10 @@ impl AgentServer for Gemini { result }) } + + fn into_any(self: Rc) -> Rc { + self + } } #[cfg(test)] diff --git a/crates/agent_settings/src/agent_profile.rs b/crates/agent_settings/src/agent_profile.rs index 402cf81678..04fdd4a753 100644 --- a/crates/agent_settings/src/agent_profile.rs +++ b/crates/agent_settings/src/agent_profile.rs @@ -58,7 +58,7 @@ impl AgentProfileSettings { || self .context_servers .get(server_id) - .map_or(false, |preset| preset.tools.get(tool_name) == Some(&true)) + .is_some_and(|preset| preset.tools.get(tool_name) == Some(&true)) } } diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index fd38ba1f7f..1fe41d002c 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -15,6 +15,8 @@ pub use crate::agent_profile::*; pub const SUMMARIZE_THREAD_PROMPT: &str = include_str!("../../agent/src/prompts/summarize_thread_prompt.txt"); +pub const SUMMARIZE_THREAD_DETAILED_PROMPT: &str = + include_str!("../../agent/src/prompts/summarize_thread_detailed_prompt.txt"); pub fn init(cx: &mut App) { AgentSettings::register(cx); @@ -116,15 +118,15 @@ pub struct LanguageModelParameters { impl LanguageModelParameters { pub fn matches(&self, model: &Arc) -> bool { - if let Some(provider) = &self.provider { - if provider.0 != model.provider_id().0 { - return false; - } + if let Some(provider) = &self.provider + && provider.0 != model.provider_id().0 + { + return false; } - if let Some(setting_model) = &self.model { - if *setting_model != model.id().0 { - return false; - } + if let Some(setting_model) = &self.model + && *setting_model != model.id().0 + { + return false; } true } diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml index fbf8590e68..43e3b25124 100644 --- a/crates/agent_ui/Cargo.toml +++ b/crates/agent_ui/Cargo.toml @@ -104,9 +104,11 @@ zed_actions.workspace = true [dev-dependencies] acp_thread = { workspace = true, features = ["test-support"] } agent = { workspace = true, features = ["test-support"] } +agent2 = { workspace = true, features = ["test-support"] } assistant_context = { workspace = true, features = ["test-support"] } assistant_tools.workspace = true buffer_diff = { workspace = true, features = ["test-support"] } +db = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] } gpui = { workspace = true, "features" = ["test-support"] } indoc.workspace = true diff --git a/crates/agent_ui/src/acp.rs b/crates/agent_ui/src/acp.rs index 831d296eeb..6f228b91d6 100644 --- a/crates/agent_ui/src/acp.rs +++ b/crates/agent_ui/src/acp.rs @@ -3,8 +3,10 @@ mod entry_view_state; mod message_editor; mod model_selector; mod model_selector_popover; +mod thread_history; mod thread_view; pub use model_selector::AcpModelSelector; pub use model_selector_popover::AcpModelSelectorPopover; +pub use thread_history::*; pub use thread_view::AcpThreadView; diff --git a/crates/agent_ui/src/acp/completion_provider.rs b/crates/agent_ui/src/acp/completion_provider.rs index 8a413fc91e..999e469d30 100644 --- a/crates/agent_ui/src/acp/completion_provider.rs +++ b/crates/agent_ui/src/acp/completion_provider.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use acp_thread::MentionUri; +use agent2::{HistoryEntry, HistoryStore}; use anyhow::Result; use editor::{CompletionProvider, Editor, ExcerptId}; use fuzzy::{StringMatch, StringMatchCandidate}; @@ -18,25 +19,21 @@ use text::{Anchor, ToPoint as _}; use ui::prelude::*; use workspace::Workspace; -use agent::thread_store::{TextThreadStore, ThreadStore}; - +use crate::AgentPanel; use crate::acp::message_editor::MessageEditor; use crate::context_picker::file_context_picker::{FileMatch, search_files}; use crate::context_picker::rules_context_picker::{RulesContextEntry, search_rules}; use crate::context_picker::symbol_context_picker::SymbolMatch; use crate::context_picker::symbol_context_picker::search_symbols; -use crate::context_picker::thread_context_picker::{ - ThreadContextEntry, ThreadMatch, search_threads, -}; use crate::context_picker::{ - ContextPickerAction, ContextPickerEntry, ContextPickerMode, RecentEntry, - available_context_picker_entries, recent_context_picker_entries, selection_ranges, + ContextPickerAction, ContextPickerEntry, ContextPickerMode, selection_ranges, }; pub(crate) enum Match { File(FileMatch), Symbol(SymbolMatch), - Thread(ThreadMatch), + Thread(HistoryEntry), + RecentThread(HistoryEntry), Fetch(SharedString), Rules(RulesContextEntry), Entry(EntryMatch), @@ -53,6 +50,7 @@ impl Match { Match::File(file) => file.mat.score, Match::Entry(mode) => mode.mat.as_ref().map(|mat| mat.score).unwrap_or(1.), Match::Thread(_) => 1., + Match::RecentThread(_) => 1., Match::Symbol(_) => 1., Match::Rules(_) => 1., Match::Fetch(_) => 1., @@ -60,209 +58,25 @@ impl Match { } } -fn search( - mode: Option, - query: String, - cancellation_flag: Arc, - recent_entries: Vec, - prompt_store: Option>, - thread_store: WeakEntity, - text_thread_context_store: WeakEntity, - workspace: Entity, - cx: &mut App, -) -> Task> { - match mode { - Some(ContextPickerMode::File) => { - let search_files_task = - search_files(query.clone(), cancellation_flag.clone(), &workspace, cx); - cx.background_spawn(async move { - search_files_task - .await - .into_iter() - .map(Match::File) - .collect() - }) - } - - Some(ContextPickerMode::Symbol) => { - let search_symbols_task = - search_symbols(query.clone(), cancellation_flag.clone(), &workspace, cx); - cx.background_spawn(async move { - search_symbols_task - .await - .into_iter() - .map(Match::Symbol) - .collect() - }) - } - - Some(ContextPickerMode::Thread) => { - if let Some((thread_store, context_store)) = thread_store - .upgrade() - .zip(text_thread_context_store.upgrade()) - { - let search_threads_task = search_threads( - query.clone(), - cancellation_flag.clone(), - thread_store, - context_store, - cx, - ); - cx.background_spawn(async move { - search_threads_task - .await - .into_iter() - .map(Match::Thread) - .collect() - }) - } else { - Task::ready(Vec::new()) - } - } - - Some(ContextPickerMode::Fetch) => { - if !query.is_empty() { - Task::ready(vec![Match::Fetch(query.into())]) - } else { - Task::ready(Vec::new()) - } - } - - Some(ContextPickerMode::Rules) => { - if let Some(prompt_store) = prompt_store.as_ref() { - let search_rules_task = - search_rules(query.clone(), cancellation_flag.clone(), prompt_store, cx); - cx.background_spawn(async move { - search_rules_task - .await - .into_iter() - .map(Match::Rules) - .collect::>() - }) - } else { - Task::ready(Vec::new()) - } - } - - None => { - if query.is_empty() { - let mut matches = recent_entries - .into_iter() - .map(|entry| match entry { - RecentEntry::File { - project_path, - path_prefix, - } => Match::File(FileMatch { - mat: fuzzy::PathMatch { - score: 1., - positions: Vec::new(), - worktree_id: project_path.worktree_id.to_usize(), - path: project_path.path, - path_prefix, - is_dir: false, - distance_to_relative_ancestor: 0, - }, - is_recent: true, - }), - RecentEntry::Thread(thread_context_entry) => Match::Thread(ThreadMatch { - thread: thread_context_entry, - is_recent: true, - }), - }) - .collect::>(); - - matches.extend( - available_context_picker_entries( - &prompt_store, - &Some(thread_store.clone()), - &workspace, - cx, - ) - .into_iter() - .map(|mode| { - Match::Entry(EntryMatch { - entry: mode, - mat: None, - }) - }), - ); - - Task::ready(matches) - } else { - let executor = cx.background_executor().clone(); - - let search_files_task = - search_files(query.clone(), cancellation_flag.clone(), &workspace, cx); - - let entries = available_context_picker_entries( - &prompt_store, - &Some(thread_store.clone()), - &workspace, - cx, - ); - let entry_candidates = entries - .iter() - .enumerate() - .map(|(ix, entry)| StringMatchCandidate::new(ix, entry.keyword())) - .collect::>(); - - cx.background_spawn(async move { - let mut matches = search_files_task - .await - .into_iter() - .map(Match::File) - .collect::>(); - - let entry_matches = fuzzy::match_strings( - &entry_candidates, - &query, - false, - true, - 100, - &Arc::new(AtomicBool::default()), - executor, - ) - .await; - - matches.extend(entry_matches.into_iter().map(|mat| { - Match::Entry(EntryMatch { - entry: entries[mat.candidate_id], - mat: Some(mat), - }) - })); - - matches.sort_by(|a, b| { - b.score() - .partial_cmp(&a.score()) - .unwrap_or(std::cmp::Ordering::Equal) - }); - - matches - }) - } - } - } -} - pub struct ContextPickerCompletionProvider { - workspace: WeakEntity, - thread_store: WeakEntity, - text_thread_store: WeakEntity, message_editor: WeakEntity, + workspace: WeakEntity, + history_store: Entity, + prompt_store: Option>, } impl ContextPickerCompletionProvider { pub fn new( - workspace: WeakEntity, - thread_store: WeakEntity, - text_thread_store: WeakEntity, message_editor: WeakEntity, + workspace: WeakEntity, + history_store: Entity, + prompt_store: Option>, ) -> Self { Self { - workspace, - thread_store, - text_thread_store, message_editor, + workspace, + history_store, + prompt_store, } } @@ -349,22 +163,13 @@ impl ContextPickerCompletionProvider { } fn completion_for_thread( - thread_entry: ThreadContextEntry, + thread_entry: HistoryEntry, source_range: Range, recent: bool, editor: WeakEntity, cx: &mut App, ) -> Completion { - let uri = match &thread_entry { - ThreadContextEntry::Thread { id, title } => MentionUri::Thread { - id: id.clone(), - name: title.to_string(), - }, - ThreadContextEntry::Context { path, title } => MentionUri::TextThread { - path: path.to_path_buf(), - name: title.to_string(), - }, - }; + let uri = thread_entry.mention_uri(); let icon_for_completion = if recent { IconName::HistoryRerun.path().into() @@ -445,19 +250,20 @@ impl ContextPickerCompletionProvider { let abs_path = project.read(cx).absolute_path(&project_path, cx)?; - let file_uri = MentionUri::File { - abs_path, - is_directory, + let uri = if is_directory { + MentionUri::Directory { abs_path } + } else { + MentionUri::File { abs_path } }; - let crease_icon_path = file_uri.icon_path(cx); + let crease_icon_path = uri.icon_path(cx); let completion_icon_path = if is_recent { IconName::HistoryRerun.path().into() } else { crease_icon_path.clone() }; - let new_text = format!("{} ", file_uri.as_link()); + let new_text = format!("{} ", uri.as_link()); let new_text_len = new_text.len(); Some(Completion { replace_range: source_range.clone(), @@ -472,7 +278,7 @@ impl ContextPickerCompletionProvider { source_range.start, new_text_len - 1, message_editor, - file_uri, + uri, )), }) } @@ -546,17 +352,262 @@ impl ContextPickerCompletionProvider { )), }) } + + fn search( + &self, + mode: Option, + query: String, + cancellation_flag: Arc, + cx: &mut App, + ) -> Task> { + let Some(workspace) = self.workspace.upgrade() else { + return Task::ready(Vec::default()); + }; + match mode { + Some(ContextPickerMode::File) => { + let search_files_task = + search_files(query.clone(), cancellation_flag.clone(), &workspace, cx); + cx.background_spawn(async move { + search_files_task + .await + .into_iter() + .map(Match::File) + .collect() + }) + } + + Some(ContextPickerMode::Symbol) => { + let search_symbols_task = + search_symbols(query.clone(), cancellation_flag.clone(), &workspace, cx); + cx.background_spawn(async move { + search_symbols_task + .await + .into_iter() + .map(Match::Symbol) + .collect() + }) + } + + Some(ContextPickerMode::Thread) => { + let search_threads_task = search_threads( + query.clone(), + cancellation_flag.clone(), + &self.history_store, + cx, + ); + cx.background_spawn(async move { + search_threads_task + .await + .into_iter() + .map(Match::Thread) + .collect() + }) + } + + Some(ContextPickerMode::Fetch) => { + if !query.is_empty() { + Task::ready(vec![Match::Fetch(query.into())]) + } else { + Task::ready(Vec::new()) + } + } + + Some(ContextPickerMode::Rules) => { + if let Some(prompt_store) = self.prompt_store.as_ref() { + let search_rules_task = + search_rules(query.clone(), cancellation_flag.clone(), prompt_store, cx); + cx.background_spawn(async move { + search_rules_task + .await + .into_iter() + .map(Match::Rules) + .collect::>() + }) + } else { + Task::ready(Vec::new()) + } + } + + None if query.is_empty() => { + let mut matches = self.recent_context_picker_entries(&workspace, cx); + + matches.extend( + self.available_context_picker_entries(&workspace, cx) + .into_iter() + .map(|mode| { + Match::Entry(EntryMatch { + entry: mode, + mat: None, + }) + }), + ); + + Task::ready(matches) + } + None => { + let executor = cx.background_executor().clone(); + + let search_files_task = + search_files(query.clone(), cancellation_flag.clone(), &workspace, cx); + + let entries = self.available_context_picker_entries(&workspace, cx); + let entry_candidates = entries + .iter() + .enumerate() + .map(|(ix, entry)| StringMatchCandidate::new(ix, entry.keyword())) + .collect::>(); + + cx.background_spawn(async move { + let mut matches = search_files_task + .await + .into_iter() + .map(Match::File) + .collect::>(); + + let entry_matches = fuzzy::match_strings( + &entry_candidates, + &query, + false, + true, + 100, + &Arc::new(AtomicBool::default()), + executor, + ) + .await; + + matches.extend(entry_matches.into_iter().map(|mat| { + Match::Entry(EntryMatch { + entry: entries[mat.candidate_id], + mat: Some(mat), + }) + })); + + matches.sort_by(|a, b| { + b.score() + .partial_cmp(&a.score()) + .unwrap_or(std::cmp::Ordering::Equal) + }); + + matches + }) + } + } + } + + fn recent_context_picker_entries( + &self, + workspace: &Entity, + cx: &mut App, + ) -> Vec { + let mut recent = Vec::with_capacity(6); + + let mut mentions = self + .message_editor + .read_with(cx, |message_editor, _cx| message_editor.mentions()) + .unwrap_or_default(); + let workspace = workspace.read(cx); + let project = workspace.project().read(cx); + + if let Some(agent_panel) = workspace.panel::(cx) + && let Some(thread) = agent_panel.read(cx).active_agent_thread(cx) + { + let thread = thread.read(cx); + mentions.insert(MentionUri::Thread { + id: thread.session_id().clone(), + name: thread.title().into(), + }); + } + + recent.extend( + workspace + .recent_navigation_history_iter(cx) + .filter(|(_, abs_path)| { + abs_path.as_ref().is_none_or(|path| { + !mentions.contains(&MentionUri::File { + abs_path: path.clone(), + }) + }) + }) + .take(4) + .filter_map(|(project_path, _)| { + project + .worktree_for_id(project_path.worktree_id, cx) + .map(|worktree| { + let path_prefix = worktree.read(cx).root_name().into(); + Match::File(FileMatch { + mat: fuzzy::PathMatch { + score: 1., + positions: Vec::new(), + worktree_id: project_path.worktree_id.to_usize(), + path: project_path.path, + path_prefix, + is_dir: false, + distance_to_relative_ancestor: 0, + }, + is_recent: true, + }) + }) + }), + ); + + const RECENT_COUNT: usize = 2; + let threads = self + .history_store + .read(cx) + .recently_opened_entries(cx) + .into_iter() + .filter(|thread| !mentions.contains(&thread.mention_uri())) + .take(RECENT_COUNT) + .collect::>(); + + recent.extend(threads.into_iter().map(Match::RecentThread)); + + recent + } + + fn available_context_picker_entries( + &self, + workspace: &Entity, + cx: &mut App, + ) -> Vec { + let mut entries = vec![ + ContextPickerEntry::Mode(ContextPickerMode::File), + ContextPickerEntry::Mode(ContextPickerMode::Symbol), + ContextPickerEntry::Mode(ContextPickerMode::Thread), + ]; + + let has_selection = workspace + .read(cx) + .active_item(cx) + .and_then(|item| item.downcast::()) + .is_some_and(|editor| { + editor.update(cx, |editor, cx| editor.has_non_empty_selection(cx)) + }); + if has_selection { + entries.push(ContextPickerEntry::Action( + ContextPickerAction::AddSelections, + )); + } + + if self.prompt_store.is_some() { + entries.push(ContextPickerEntry::Mode(ContextPickerMode::Rules)); + } + + entries.push(ContextPickerEntry::Mode(ContextPickerMode::Fetch)); + + entries + } } fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel { let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); let mut label = CodeLabel::default(); - label.push_str(&file_name, None); + label.push_str(file_name, None); label.push_str(" ", None); if let Some(directory) = directory { - label.push_str(&directory, comment_id); + label.push_str(directory, comment_id); } label.filter_range = 0..label.text().len(); @@ -595,45 +646,12 @@ impl CompletionProvider for ContextPickerCompletionProvider { let source_range = snapshot.anchor_before(state.source_range.start) ..snapshot.anchor_after(state.source_range.end); - let thread_store = self.thread_store.clone(); - let text_thread_store = self.text_thread_store.clone(); let editor = self.message_editor.clone(); - let Ok((exclude_paths, exclude_threads)) = - self.message_editor.update(cx, |message_editor, _cx| { - message_editor.mentioned_path_and_threads() - }) - else { - return Task::ready(Ok(Vec::new())); - }; let MentionCompletion { mode, argument, .. } = state; let query = argument.unwrap_or_else(|| "".to_string()); - let recent_entries = recent_context_picker_entries( - Some(thread_store.clone()), - Some(text_thread_store.clone()), - workspace.clone(), - &exclude_paths, - &exclude_threads, - cx, - ); - - let prompt_store = thread_store - .read_with(cx, |thread_store, _cx| thread_store.prompt_store().clone()) - .ok() - .flatten(); - - let search_task = search( - mode, - query, - Arc::::default(), - recent_entries, - prompt_store, - thread_store.clone(), - text_thread_store.clone(), - workspace.clone(), - cx, - ); + let search_task = self.search(mode, query, Arc::::default(), cx); cx.spawn(async move |_, cx| { let matches = search_task.await; @@ -668,12 +686,18 @@ impl CompletionProvider for ContextPickerCompletionProvider { cx, ), - Match::Thread(ThreadMatch { - thread, is_recent, .. - }) => Some(Self::completion_for_thread( + Match::Thread(thread) => Some(Self::completion_for_thread( thread, source_range.clone(), - is_recent, + false, + editor.clone(), + cx, + )), + + Match::RecentThread(thread) => Some(Self::completion_for_thread( + thread, + source_range.clone(), + true, editor.clone(), cx, )), @@ -747,6 +771,42 @@ impl CompletionProvider for ContextPickerCompletionProvider { } } +pub(crate) fn search_threads( + query: String, + cancellation_flag: Arc, + history_store: &Entity, + cx: &mut App, +) -> Task> { + let threads = history_store.read(cx).entries(cx); + if query.is_empty() { + return Task::ready(threads); + } + + let executor = cx.background_executor().clone(); + cx.background_spawn(async move { + let candidates = threads + .iter() + .enumerate() + .map(|(id, thread)| StringMatchCandidate::new(id, thread.title())) + .collect::>(); + let matches = fuzzy::match_strings( + &candidates, + &query, + false, + true, + 100, + &cancellation_flag, + executor, + ) + .await; + + matches + .into_iter() + .map(|mat| threads[mat.candidate_id].clone()) + .collect() + }) +} + fn confirm_completion_callback( crease_text: SharedString, start: Anchor, @@ -762,14 +822,16 @@ fn confirm_completion_callback( message_editor .clone() .update(cx, |message_editor, cx| { - message_editor.confirm_completion( - crease_text, - start, - content_len, - mention_uri, - window, - cx, - ) + message_editor + .confirm_completion( + crease_text, + start, + content_len, + mention_uri, + window, + cx, + ) + .detach(); }) .ok(); }); @@ -794,7 +856,7 @@ impl MentionCompletion { && line .chars() .nth(last_mention_start - 1) - .map_or(false, |c| !c.is_whitespace()) + .is_some_and(|c| !c.is_whitespace()) { return None; } diff --git a/crates/agent_ui/src/acp/entry_view_state.rs b/crates/agent_ui/src/acp/entry_view_state.rs index e99d1f6323..67acbb8b5b 100644 --- a/crates/agent_ui/src/acp/entry_view_state.rs +++ b/crates/agent_ui/src/acp/entry_view_state.rs @@ -1,15 +1,16 @@ use std::ops::Range; use acp_thread::{AcpThread, AgentThreadEntry}; -use agent::{TextThreadStore, ThreadStore}; +use agent2::HistoryStore; use collections::HashMap; use editor::{Editor, EditorMode, MinimapVisibility}; use gpui::{ - AnyEntity, App, AppContext as _, Entity, EntityId, EventEmitter, TextStyleRefinement, - WeakEntity, Window, + AnyEntity, App, AppContext as _, Entity, EntityId, EventEmitter, Focusable, + TextStyleRefinement, WeakEntity, Window, }; use language::language_settings::SoftWrap; use project::Project; +use prompt_store::PromptStore; use settings::Settings as _; use terminal_view::TerminalView; use theme::ThemeSettings; @@ -21,24 +22,27 @@ use crate::acp::message_editor::{MessageEditor, MessageEditorEvent}; pub struct EntryViewState { workspace: WeakEntity, project: Entity, - thread_store: Entity, - text_thread_store: Entity, + history_store: Entity, + prompt_store: Option>, entries: Vec, + prevent_slash_commands: bool, } impl EntryViewState { pub fn new( workspace: WeakEntity, project: Entity, - thread_store: Entity, - text_thread_store: Entity, + history_store: Entity, + prompt_store: Option>, + prevent_slash_commands: bool, ) -> Self { Self { workspace, project, - thread_store, - text_thread_store, + history_store, + prompt_store, entries: Vec::new(), + prevent_slash_commands, } } @@ -61,33 +65,45 @@ impl EntryViewState { AgentThreadEntry::UserMessage(message) => { let has_id = message.id.is_some(); let chunks = message.chunks.clone(); - let message_editor = cx.new(|cx| { - let mut editor = MessageEditor::new( - self.workspace.clone(), - self.project.clone(), - self.thread_store.clone(), - self.text_thread_store.clone(), - editor::EditorMode::AutoHeight { - min_lines: 1, - max_lines: None, - }, - window, - cx, - ); - if !has_id { - editor.set_read_only(true, cx); + if let Some(Entry::UserMessage(editor)) = self.entries.get_mut(index) { + if !editor.focus_handle(cx).is_focused(window) { + // Only update if we are not editing. + // If we are, cancelling the edit will set the message to the newest content. + editor.update(cx, |editor, cx| { + editor.set_message(chunks, window, cx); + }); } - editor.set_message(chunks, window, cx); - editor - }); - cx.subscribe(&message_editor, move |_, editor, event, cx| { - cx.emit(EntryViewEvent { - entry_index: index, - view_event: ViewEvent::MessageEditorEvent(editor, *event), + } else { + let message_editor = cx.new(|cx| { + let mut editor = MessageEditor::new( + self.workspace.clone(), + self.project.clone(), + self.history_store.clone(), + self.prompt_store.clone(), + "Edit message - @ to include context", + self.prevent_slash_commands, + editor::EditorMode::AutoHeight { + min_lines: 1, + max_lines: None, + }, + window, + cx, + ); + if !has_id { + editor.set_read_only(true, cx); + } + editor.set_message(chunks, window, cx); + editor + }); + cx.subscribe(&message_editor, move |_, editor, event, cx| { + cx.emit(EntryViewEvent { + entry_index: index, + view_event: ViewEvent::MessageEditorEvent(editor, *event), + }) }) - }) - .detach(); - self.set_entry(index, Entry::UserMessage(message_editor)); + .detach(); + self.set_entry(index, Entry::UserMessage(message_editor)); + } } AgentThreadEntry::ToolCall(tool_call) => { let terminals = tool_call.terminals().cloned().collect::>(); @@ -174,6 +190,7 @@ pub enum ViewEvent { MessageEditorEvent(Entity, MessageEditorEvent), } +#[derive(Debug)] pub enum Entry { UserMessage(Entity), Content(HashMap), @@ -297,9 +314,10 @@ mod tests { use std::{path::Path, rc::Rc}; use acp_thread::{AgentConnection, StubAgentConnection}; - use agent::{TextThreadStore, ThreadStore}; use agent_client_protocol as acp; use agent_settings::AgentSettings; + use agent2::HistoryStore; + use assistant_context::ContextStore; use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind}; use editor::{EditorSettings, RowInfo}; use fs::FakeFs; @@ -362,15 +380,16 @@ mod tests { connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx) }); - let thread_store = cx.new(|cx| ThreadStore::fake(project.clone(), cx)); - let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx)); + let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx)); + let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); let view_state = cx.new(|_cx| { EntryViewState::new( workspace.downgrade(), project.clone(), - thread_store, - text_thread_store, + history_store, + None, + false, ) }); diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 12766ef458..c87c824015 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -3,60 +3,72 @@ use crate::{ context_picker::fetch_context_picker::fetch_url_content, }; use acp_thread::{MentionUri, selection_name}; -use agent::{TextThreadStore, ThreadId, ThreadStore}; use agent_client_protocol as acp; +use agent_servers::AgentServer; +use agent2::HistoryStore; use anyhow::{Context as _, Result, anyhow}; +use assistant_slash_commands::codeblock_fence_for_path; use collections::{HashMap, HashSet}; use editor::{ - Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, - EditorMode, EditorStyle, ExcerptId, FoldPlaceholder, MultiBuffer, ToOffset, + Addon, Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, + EditorEvent, EditorMode, EditorStyle, ExcerptId, FoldPlaceholder, MultiBuffer, + SemanticsProvider, ToOffset, actions::Paste, display_map::{Crease, CreaseId, FoldId}, }; use futures::{ FutureExt as _, TryFutureExt as _, - future::{Shared, try_join_all}, + future::{Shared, join_all, try_join_all}, }; use gpui::{ - AppContext, ClipboardEntry, Context, Entity, EventEmitter, FocusHandle, Focusable, Image, - ImageFormat, Img, Task, TextStyle, WeakEntity, + AppContext, ClipboardEntry, Context, Entity, EventEmitter, FocusHandle, Focusable, + HighlightStyle, Image, ImageFormat, Img, KeyContext, Subscription, Task, TextStyle, + UnderlineStyle, WeakEntity, }; use language::{Buffer, Language}; use language_model::LanguageModelImage; -use project::{CompletionIntent, Project}; +use project::{Project, ProjectPath, Worktree}; +use prompt_store::PromptStore; use rope::Point; use settings::Settings; use std::{ + cell::Cell, ffi::OsStr, - fmt::Write, + fmt::{Display, Write}, ops::Range, path::{Path, PathBuf}, rc::Rc, sync::Arc, + time::Duration, }; -use text::OffsetRangeExt; +use text::{OffsetRangeExt, ToOffset as _}; use theme::ThemeSettings; use ui::{ ActiveTheme, AnyElement, App, ButtonCommon, ButtonLike, ButtonStyle, Color, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ParentElement, Render, SelectableButton, SharedString, Styled, TextSize, TintColor, Toggleable, Window, div, - h_flex, + h_flex, px, }; use url::Url; use util::ResultExt; use workspace::{Workspace, notifications::NotifyResultExt as _}; use zed_actions::agent::Chat; +const PARSE_SLASH_COMMAND_DEBOUNCE: Duration = Duration::from_millis(50); + pub struct MessageEditor { mention_set: MentionSet, editor: Entity, project: Entity, workspace: WeakEntity, - thread_store: Entity, - text_thread_store: Entity, + history_store: Entity, + prompt_store: Option>, + prevent_slash_commands: bool, + _subscriptions: Vec, + _parse_slash_command_task: Task<()>, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum MessageEditorEvent { Send, Cancel, @@ -69,8 +81,10 @@ impl MessageEditor { pub fn new( workspace: WeakEntity, project: Entity, - thread_store: Entity, - text_thread_store: Entity, + history_store: Entity, + prompt_store: Option>, + placeholder: impl Into>, + prevent_slash_commands: bool, mode: EditorMode, window: &mut Window, cx: &mut Context, @@ -83,18 +97,21 @@ impl MessageEditor { None, ); let completion_provider = ContextPickerCompletionProvider::new( - workspace.clone(), - thread_store.downgrade(), - text_thread_store.downgrade(), cx.weak_entity(), + workspace.clone(), + history_store.clone(), + prompt_store.clone(), ); + let semantics_provider = Rc::new(SlashCommandSemanticsProvider { + range: Cell::new(None), + }); let mention_set = MentionSet::default(); let editor = cx.new(|cx| { let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); let mut editor = Editor::new(mode, buffer, None, window, cx); - editor.set_placeholder_text("Message the agent - @ to include files", cx); + editor.set_placeholder_text(placeholder, cx); editor.set_show_indent_guides(false, cx); editor.set_soft_wrap(); editor.set_use_modal_editing(true); @@ -104,6 +121,10 @@ impl MessageEditor { max_entries_visible: 12, placement: Some(ContextMenuPlacement::Above), }); + if prevent_slash_commands { + editor.set_semantics_provider(Some(semantics_provider.clone())); + } + editor.register_addon(MessageEditorAddon::new()); editor }); @@ -112,13 +133,33 @@ impl MessageEditor { }) .detach(); + let mut subscriptions = Vec::new(); + if prevent_slash_commands { + subscriptions.push(cx.subscribe_in(&editor, window, { + let semantics_provider = semantics_provider.clone(); + move |this, editor, event, window, cx| { + if let EditorEvent::Edited { .. } = event { + this.highlight_slash_command( + semantics_provider.clone(), + editor.clone(), + window, + cx, + ); + } + } + })); + } + Self { editor, project, mention_set, - thread_store, - text_thread_store, workspace, + history_store, + prompt_store, + prevent_slash_commands, + _subscriptions: subscriptions, + _parse_slash_command_task: Task::ready(()), } } @@ -136,23 +177,12 @@ impl MessageEditor { self.editor.read(cx).is_empty(cx) } - pub fn mentioned_path_and_threads(&self) -> (HashSet, HashSet) { - let mut excluded_paths = HashSet::default(); - let mut excluded_threads = HashSet::default(); - - for uri in self.mention_set.uri_by_crease_id.values() { - match uri { - MentionUri::File { abs_path, .. } => { - excluded_paths.insert(abs_path.clone()); - } - MentionUri::Thread { id, .. } => { - excluded_threads.insert(id.clone()); - } - _ => {} - } - } - - (excluded_paths, excluded_threads) + pub fn mentions(&self) -> HashSet { + self.mention_set + .uri_by_crease_id + .values() + .cloned() + .collect() } pub fn confirm_completion( @@ -163,20 +193,69 @@ impl MessageEditor { mention_uri: MentionUri, window: &mut Window, cx: &mut Context, - ) { + ) -> Task<()> { let snapshot = self .editor .update(cx, |editor, cx| editor.snapshot(window, cx)); let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot.as_singleton() else { - return; + return Task::ready(()); }; let Some(anchor) = snapshot .buffer_snapshot .anchor_in_excerpt(*excerpt_id, start) else { - return; + return Task::ready(()); }; + if let MentionUri::File { abs_path, .. } = &mention_uri { + let extension = abs_path + .extension() + .and_then(OsStr::to_str) + .unwrap_or_default(); + + if Img::extensions().contains(&extension) && !extension.contains("svg") { + let project = self.project.clone(); + let Some(project_path) = project + .read(cx) + .project_path_for_absolute_path(abs_path, cx) + else { + return Task::ready(()); + }; + let image = cx + .spawn(async move |_, cx| { + let image = project + .update(cx, |project, cx| project.open_image(project_path, cx)) + .map_err(|e| e.to_string())? + .await + .map_err(|e| e.to_string())?; + image + .read_with(cx, |image, _cx| image.image.clone()) + .map_err(|e| e.to_string()) + }) + .shared(); + let Some(crease_id) = insert_crease_for_image( + *excerpt_id, + start, + content_len, + Some(abs_path.as_path().into()), + image.clone(), + self.editor.clone(), + window, + cx, + ) else { + return Task::ready(()); + }; + return self.confirm_mention_for_image( + crease_id, + anchor, + Some(abs_path.clone()), + image, + window, + cx, + ); + } + } + let Some(crease_id) = crate::context_picker::insert_crease_for_mention( *excerpt_id, start, @@ -187,76 +266,153 @@ impl MessageEditor { window, cx, ) else { - return; + return Task::ready(()); }; match mention_uri { MentionUri::Fetch { url } => { - self.confirm_mention_for_fetch(crease_id, anchor, url, window, cx); + self.confirm_mention_for_fetch(crease_id, anchor, url, window, cx) } - MentionUri::File { - abs_path, - is_directory, - } => { - self.confirm_mention_for_file( - crease_id, - anchor, - abs_path, - is_directory, - window, - cx, - ); + MentionUri::Directory { abs_path } => { + self.confirm_mention_for_directory(crease_id, anchor, abs_path, window, cx) } MentionUri::Thread { id, name } => { - self.confirm_mention_for_thread(crease_id, anchor, id, name, window, cx); + self.confirm_mention_for_thread(crease_id, anchor, id, name, window, cx) } MentionUri::TextThread { path, name } => { - self.confirm_mention_for_text_thread(crease_id, anchor, path, name, window, cx); + self.confirm_mention_for_text_thread(crease_id, anchor, path, name, window, cx) } - MentionUri::Symbol { .. } | MentionUri::Rule { .. } | MentionUri::Selection { .. } => { + MentionUri::File { .. } + | MentionUri::Symbol { .. } + | MentionUri::Rule { .. } + | MentionUri::Selection { .. } => { self.mention_set.insert_uri(crease_id, mention_uri.clone()); + Task::ready(()) } } } - fn confirm_mention_for_file( + fn confirm_mention_for_directory( &mut self, crease_id: CreaseId, anchor: Anchor, abs_path: PathBuf, - is_directory: bool, window: &mut Window, cx: &mut Context, - ) { - let extension = abs_path - .extension() - .and_then(OsStr::to_str) - .unwrap_or_default(); + ) -> Task<()> { + fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc, PathBuf)> { + let mut files = Vec::new(); - if Img::extensions().contains(&extension) && !extension.contains("svg") { - let project = self.project.clone(); - let Some(project_path) = project - .read(cx) - .project_path_for_absolute_path(&abs_path, cx) - else { - return; - }; - let image = cx.spawn(async move |_, cx| { - let image = project - .update(cx, |project, cx| project.open_image(project_path, cx))? - .await?; - image.read_with(cx, |image, _cx| image.image.clone()) - }); - self.confirm_mention_for_image(crease_id, anchor, Some(abs_path), image, window, cx); - } else { - self.mention_set.insert_uri( - crease_id, - MentionUri::File { - abs_path, - is_directory, - }, - ); + for entry in worktree.child_entries(path) { + if entry.is_dir() { + files.extend(collect_files_in_path(worktree, &entry.path)); + } else if entry.is_file() { + files.push((entry.path.clone(), worktree.full_path(&entry.path))); + } + } + + files } + + let uri = MentionUri::Directory { + abs_path: abs_path.clone(), + }; + let Some(project_path) = self + .project + .read(cx) + .project_path_for_absolute_path(&abs_path, cx) + else { + return Task::ready(()); + }; + let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else { + return Task::ready(()); + }; + let Some(worktree) = self.project.read(cx).worktree_for_entry(entry.id, cx) else { + return Task::ready(()); + }; + let project = self.project.clone(); + let task = cx.spawn(async move |_, cx| { + let directory_path = entry.path.clone(); + + let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id())?; + let file_paths = worktree.read_with(cx, |worktree, _cx| { + collect_files_in_path(worktree, &directory_path) + })?; + let descendants_future = cx.update(|cx| { + join_all(file_paths.into_iter().map(|(worktree_path, full_path)| { + let rel_path = worktree_path + .strip_prefix(&directory_path) + .log_err() + .map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into()); + + let open_task = project.update(cx, |project, cx| { + project.buffer_store().update(cx, |buffer_store, cx| { + let project_path = ProjectPath { + worktree_id, + path: worktree_path, + }; + buffer_store.open_buffer(project_path, cx) + }) + }); + + // TODO: report load errors instead of just logging + let rope_task = cx.spawn(async move |cx| { + let buffer = open_task.await.log_err()?; + let rope = buffer + .read_with(cx, |buffer, _cx| buffer.as_rope().clone()) + .log_err()?; + Some(rope) + }); + + cx.background_spawn(async move { + let rope = rope_task.await?; + Some((rel_path, full_path, rope.to_string())) + }) + })) + })?; + + let contents = cx + .background_spawn(async move { + let contents = descendants_future.await.into_iter().flatten(); + contents.collect() + }) + .await; + anyhow::Ok(contents) + }); + let task = cx + .spawn(async move |_, _| { + task.await + .map(|contents| DirectoryContents(contents).to_string()) + .map_err(|e| e.to_string()) + }) + .shared(); + + self.mention_set + .directories + .insert(abs_path.clone(), task.clone()); + + let editor = self.editor.clone(); + cx.spawn_in(window, async move |this, cx| { + if task.await.notify_async_err(cx).is_some() { + this.update(cx, |this, _| { + this.mention_set.insert_uri(crease_id, uri); + }) + .ok(); + } else { + editor + .update(cx, |editor, cx| { + editor.display_map.update(cx, |display_map, cx| { + display_map.unfold_intersecting(vec![anchor..anchor], true, cx); + }); + editor.remove_creases([crease_id], cx); + }) + .ok(); + this.update(cx, |this, _cx| { + this.mention_set.directories.remove(&abs_path); + }) + .ok(); + } + }) } fn confirm_mention_for_fetch( @@ -266,13 +422,13 @@ impl MessageEditor { url: url::Url, window: &mut Window, cx: &mut Context, - ) { + ) -> Task<()> { let Some(http_client) = self .workspace .update(cx, |workspace, _cx| workspace.client().http_client()) .ok() else { - return; + return Task::ready(()); }; let url_string = url.to_string(); @@ -290,9 +446,9 @@ impl MessageEditor { cx.spawn_in(window, async move |this, cx| { let fetch = fetch.await.notify_async_err(cx); this.update(cx, |this, cx| { - let mention_uri = MentionUri::Fetch { url }; if fetch.is_some() { - this.mention_set.insert_uri(crease_id, mention_uri.clone()); + this.mention_set + .insert_uri(crease_id, MentionUri::Fetch { url }); } else { // Remove crease if we failed to fetch this.editor.update(cx, |editor, cx| { @@ -301,11 +457,11 @@ impl MessageEditor { }); editor.remove_creases([crease_id], cx); }); + this.mention_set.fetch_results.remove(&url); } }) .ok(); }) - .detach(); } pub fn confirm_mention_for_selection( @@ -360,6 +516,119 @@ impl MessageEditor { } } + fn confirm_mention_for_thread( + &mut self, + crease_id: CreaseId, + anchor: Anchor, + id: acp::SessionId, + name: String, + window: &mut Window, + cx: &mut Context, + ) -> Task<()> { + let uri = MentionUri::Thread { + id: id.clone(), + name, + }; + let server = Rc::new(agent2::NativeAgentServer::new( + self.project.read(cx).fs().clone(), + self.history_store.clone(), + )); + let connection = server.connect(Path::new(""), &self.project, cx); + let load_summary = cx.spawn({ + let id = id.clone(); + async move |_, cx| { + let agent = connection.await?; + let agent = agent.downcast::().unwrap(); + let summary = agent + .0 + .update(cx, |agent, cx| agent.thread_summary(id, cx))? + .await?; + anyhow::Ok(summary) + } + }); + let task = cx + .spawn(async move |_, _| load_summary.await.map_err(|e| format!("{e}"))) + .shared(); + + self.mention_set.insert_thread(id.clone(), task.clone()); + + let editor = self.editor.clone(); + cx.spawn_in(window, async move |this, cx| { + if task.await.notify_async_err(cx).is_some() { + this.update(cx, |this, _| { + this.mention_set.insert_uri(crease_id, uri); + }) + .ok(); + } else { + editor + .update(cx, |editor, cx| { + editor.display_map.update(cx, |display_map, cx| { + display_map.unfold_intersecting(vec![anchor..anchor], true, cx); + }); + editor.remove_creases([crease_id], cx); + }) + .ok(); + this.update(cx, |this, _| { + this.mention_set.thread_summaries.remove(&id); + }) + .ok(); + } + }) + } + + fn confirm_mention_for_text_thread( + &mut self, + crease_id: CreaseId, + anchor: Anchor, + path: PathBuf, + name: String, + window: &mut Window, + cx: &mut Context, + ) -> Task<()> { + let uri = MentionUri::TextThread { + path: path.clone(), + name, + }; + let context = self.history_store.update(cx, |text_thread_store, cx| { + text_thread_store.load_text_thread(path.as_path().into(), cx) + }); + let task = cx + .spawn(async move |_, cx| { + let context = context.await.map_err(|e| e.to_string())?; + let xml = context + .update(cx, |context, cx| context.to_xml(cx)) + .map_err(|e| e.to_string())?; + Ok(xml) + }) + .shared(); + + self.mention_set + .insert_text_thread(path.clone(), task.clone()); + + let editor = self.editor.clone(); + cx.spawn_in(window, async move |this, cx| { + if task.await.notify_async_err(cx).is_some() { + this.update(cx, |this, _| { + this.mention_set.insert_uri(crease_id, uri); + }) + .ok(); + } else { + editor + .update(cx, |editor, cx| { + editor.display_map.update(cx, |display_map, cx| { + display_map.unfold_intersecting(vec![anchor..anchor], true, cx); + }); + editor.remove_creases([crease_id], cx); + }) + .ok(); + this.update(cx, |this, _| { + this.mention_set.text_thread_summaries.remove(&path); + }) + .ok(); + } + }) + } + pub fn contents( &self, window: &mut Window, @@ -367,8 +636,9 @@ impl MessageEditor { ) -> Task>> { let contents = self.mention_set - .contents(self.project.clone(), self.thread_store.clone(), window, cx); + .contents(&self.project, self.prompt_store.as_ref(), window, cx); let editor = self.editor.clone(); + let prevent_slash_commands = self.prevent_slash_commands; cx.spawn(async move |_, cx| { let contents = contents.await?; @@ -391,7 +661,15 @@ impl MessageEditor { let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot); if crease_range.start > ix { - chunks.push(text[ix..crease_range.start].into()); + let chunk = if prevent_slash_commands + && ix == 0 + && parse_slash_command(&text[ix..]).is_some() + { + format!(" {}", &text[ix..crease_range.start]).into() + } else { + text[ix..crease_range.start].into() + }; + chunks.push(chunk); } let chunk = match mention { Mention::Text { uri, content } => { @@ -423,7 +701,14 @@ impl MessageEditor { } if ix < text.len() { - let last_chunk = text[ix..].trim_end(); + let last_chunk = if prevent_slash_commands + && ix == 0 + && parse_slash_command(&text[ix..]).is_some() + { + format!(" {}", text[ix..].trim_end()) + } else { + text[ix..].trim_end().to_owned() + }; if !last_chunk.is_empty() { chunks.push(last_chunk.into()); } @@ -443,6 +728,9 @@ impl MessageEditor { } fn send(&mut self, _: &Chat, _: &mut Window, cx: &mut Context) { + if self.is_empty(cx) { + return; + } cx.emit(MessageEditorEvent::Send) } @@ -497,31 +785,28 @@ impl MessageEditor { let Some(anchor) = multibuffer_anchor else { return; }; + let task = Task::ready(Ok(Arc::new(image))).shared(); let Some(crease_id) = insert_crease_for_image( excerpt_id, text_anchor, content_len, None.clone(), + task.clone(), self.editor.clone(), window, cx, ) else { return; }; - self.confirm_mention_for_image( - crease_id, - anchor, - None, - Task::ready(Ok(Arc::new(image))), - window, - cx, - ); + self.confirm_mention_for_image(crease_id, anchor, None, task, window, cx) + .detach(); } } pub fn insert_dragged_files( - &self, + &mut self, paths: Vec, + added_worktrees: Vec>, window: &mut Window, cx: &mut Context, ) { @@ -529,6 +814,7 @@ impl MessageEditor { let Some(buffer) = buffer.read(cx).as_singleton() else { return; }; + let mut tasks = Vec::new(); for path in paths { let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else { continue; @@ -536,39 +822,44 @@ impl MessageEditor { let Some(abs_path) = self.project.read(cx).absolute_path(&path, cx) else { continue; }; - - let anchor = buffer.update(cx, |buffer, _cx| buffer.anchor_before(buffer.len())); let path_prefix = abs_path .file_name() .unwrap_or(path.path.as_os_str()) .display() .to_string(); - let Some(completion) = ContextPickerCompletionProvider::completion_for_path( - path, - &path_prefix, - false, - entry.is_dir(), - anchor..anchor, - cx.weak_entity(), - self.project.clone(), - cx, - ) else { - continue; + let (file_name, _) = + crate::context_picker::file_context_picker::extract_file_name_and_directory( + &path.path, + &path_prefix, + ); + + let uri = if entry.is_dir() { + MentionUri::Directory { abs_path } + } else { + MentionUri::File { abs_path } }; + let new_text = format!("{} ", uri.as_link()); + let content_len = new_text.len() - 1; + + let anchor = buffer.update(cx, |buffer, _cx| buffer.anchor_before(buffer.len())); + self.editor.update(cx, |message_editor, cx| { message_editor.edit( [( multi_buffer::Anchor::max()..multi_buffer::Anchor::max(), - completion.new_text, + new_text, )], cx, ); }); - if let Some(confirm) = completion.confirm.clone() { - confirm(CompletionIntent::Complete, window, cx); - } + tasks.push(self.confirm_completion(file_name, anchor, content_len, uri, window, cx)); } + cx.spawn(async move |_, _| { + join_all(tasks).await; + drop(added_worktrees); + }) + .detach(); } pub fn set_read_only(&mut self, read_only: bool, cx: &mut Context) { @@ -583,10 +874,10 @@ impl MessageEditor { crease_id: CreaseId, anchor: Anchor, abs_path: Option, - image: Task>>, + image: Shared, String>>>, window: &mut Window, cx: &mut Context, - ) { + ) -> Task<()> { let editor = self.editor.clone(); let task = cx .spawn_in(window, { @@ -617,13 +908,8 @@ impl MessageEditor { if task.await.notify_async_err(cx).is_some() { if let Some(abs_path) = abs_path.clone() { this.update(cx, |this, _cx| { - this.mention_set.insert_uri( - crease_id, - MentionUri::File { - abs_path, - is_directory: false, - }, - ); + this.mention_set + .insert_uri(crease_id, MentionUri::File { abs_path }); }) .ok(); } @@ -636,107 +922,12 @@ impl MessageEditor { editor.remove_creases([crease_id], cx); }) .ok(); - } - }) - .detach(); - } - - fn confirm_mention_for_thread( - &mut self, - crease_id: CreaseId, - anchor: Anchor, - id: ThreadId, - name: String, - window: &mut Window, - cx: &mut Context, - ) { - let uri = MentionUri::Thread { - id: id.clone(), - name, - }; - let open_task = self.thread_store.update(cx, |thread_store, cx| { - thread_store.open_thread(&id, window, cx) - }); - let task = cx - .spawn(async move |_, cx| { - let thread = open_task.await.map_err(|e| e.to_string())?; - let content = thread - .read_with(cx, |thread, _cx| thread.latest_detailed_summary_or_text()) - .map_err(|e| e.to_string())?; - Ok(content) - }) - .shared(); - - self.mention_set.insert_thread(id, task.clone()); - - let editor = self.editor.clone(); - cx.spawn_in(window, async move |this, cx| { - if task.await.notify_async_err(cx).is_some() { - this.update(cx, |this, _| { - this.mention_set.insert_uri(crease_id, uri); + this.update(cx, |this, _cx| { + this.mention_set.images.remove(&crease_id); }) .ok(); - } else { - editor - .update(cx, |editor, cx| { - editor.display_map.update(cx, |display_map, cx| { - display_map.unfold_intersecting(vec![anchor..anchor], true, cx); - }); - editor.remove_creases([crease_id], cx); - }) - .ok(); } }) - .detach(); - } - - fn confirm_mention_for_text_thread( - &mut self, - crease_id: CreaseId, - anchor: Anchor, - path: PathBuf, - name: String, - window: &mut Window, - cx: &mut Context, - ) { - let uri = MentionUri::TextThread { - path: path.clone(), - name, - }; - let context = self.text_thread_store.update(cx, |text_thread_store, cx| { - text_thread_store.open_local_context(path.as_path().into(), cx) - }); - let task = cx - .spawn(async move |_, cx| { - let context = context.await.map_err(|e| e.to_string())?; - let xml = context - .update(cx, |context, cx| context.to_xml(cx)) - .map_err(|e| e.to_string())?; - Ok(xml) - }) - .shared(); - - self.mention_set.insert_text_thread(path, task.clone()); - - let editor = self.editor.clone(); - cx.spawn_in(window, async move |this, cx| { - if task.await.notify_async_err(cx).is_some() { - this.update(cx, |this, _| { - this.mention_set.insert_uri(crease_id, uri); - }) - .ok(); - } else { - editor - .update(cx, |editor, cx| { - editor.display_map.update(cx, |display_map, cx| { - display_map.unfold_intersecting(vec![anchor..anchor], true, cx); - }); - editor.remove_creases([crease_id], cx); - }) - .ok(); - } - }) - .detach(); } pub fn set_mode(&mut self, mode: EditorMode, cx: &mut Context) { @@ -821,6 +1012,10 @@ impl MessageEditor { self.mention_set .add_fetch_result(url, Task::ready(Ok(text)).shared()); } + MentionUri::Directory { abs_path } => { + let task = Task::ready(Ok(text)).shared(); + self.mention_set.directories.insert(abs_path, task); + } MentionUri::File { .. } | MentionUri::Symbol { .. } | MentionUri::Rule { .. } @@ -873,6 +1068,48 @@ impl MessageEditor { cx.notify(); } + fn highlight_slash_command( + &mut self, + semantics_provider: Rc, + editor: Entity, + window: &mut Window, + cx: &mut Context, + ) { + struct InvalidSlashCommand; + + self._parse_slash_command_task = cx.spawn_in(window, async move |_, cx| { + cx.background_executor() + .timer(PARSE_SLASH_COMMAND_DEBOUNCE) + .await; + editor + .update_in(cx, |editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let range = parse_slash_command(&editor.text(cx)); + semantics_provider.range.set(range); + if let Some((start, end)) = range { + editor.highlight_text::( + vec![ + snapshot.buffer_snapshot.anchor_after(start) + ..snapshot.buffer_snapshot.anchor_before(end), + ], + HighlightStyle { + underline: Some(UnderlineStyle { + thickness: px(1.), + color: Some(gpui::red()), + wavy: true, + }), + ..Default::default() + }, + cx, + ); + } else { + editor.clear_highlights::(cx); + } + }) + .ok(); + }) + } + #[cfg(test)] pub fn set_text(&mut self, text: &str, window: &mut Window, cx: &mut Context) { self.editor.update(cx, |editor, cx| { @@ -886,6 +1123,18 @@ impl MessageEditor { } } +struct DirectoryContents(Arc<[(Arc, PathBuf, String)]>); + +impl Display for DirectoryContents { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (_relative_path, full_path, content) in self.0.iter() { + let fence = codeblock_fence_for_path(Some(full_path), None); + write!(f, "\n{fence}\n{content}\n```")?; + } + Ok(()) + } +} + impl Focusable for MessageEditor { fn focus_handle(&self, cx: &App) -> FocusHandle { self.editor.focus_handle(cx) @@ -936,6 +1185,7 @@ pub(crate) fn insert_crease_for_image( anchor: text::Anchor, content_len: usize, abs_path: Option>, + image: Shared, String>>>, editor: Entity, window: &mut Window, cx: &mut App, @@ -955,7 +1205,7 @@ pub(crate) fn insert_crease_for_image( let end = snapshot.anchor_before(start.to_offset(&snapshot) + content_len); let placeholder = FoldPlaceholder { - render: render_image_fold_icon_button(crease_label, cx.weak_entity()), + render: render_image_fold_icon_button(crease_label, image, cx.weak_entity()), merge_adjacent: false, ..Default::default() }; @@ -977,9 +1227,11 @@ pub(crate) fn insert_crease_for_image( fn render_image_fold_icon_button( label: SharedString, + image_task: Shared, String>>>, editor: WeakEntity, ) -> Arc, &mut App) -> AnyElement> { Arc::new({ + let image_task = image_task.clone(); move |fold_id, fold_range, cx| { let is_in_text_selection = editor .update(cx, |editor, cx| editor.is_range_selected(&fold_range, cx)) @@ -1004,11 +1256,47 @@ fn render_image_fold_icon_button( .single_line(), ), ) + .hoverable_tooltip({ + let image_task = image_task.clone(); + move |_, cx| { + let image = image_task.peek().cloned().transpose().ok().flatten(); + let image_task = image_task.clone(); + cx.new::(|cx| ImageHover { + image, + _task: cx.spawn(async move |this, cx| { + if let Ok(image) = image_task.clone().await { + this.update(cx, |this, cx| { + if this.image.replace(image).is_none() { + cx.notify(); + } + }) + .ok(); + } + }), + }) + .into() + } + }) .into_any_element() } }) } +struct ImageHover { + image: Option>, + _task: Task<()>, +} + +impl Render for ImageHover { + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + if let Some(image) = self.image.clone() { + gpui::img(image).max_w_96().max_h_96().into_any_element() + } else { + gpui::Empty.into_any_element() + } + } +} + #[derive(Debug, Eq, PartialEq)] pub enum Mention { Text { uri: MentionUri, content: String }, @@ -1027,8 +1315,9 @@ pub struct MentionSet { uri_by_crease_id: HashMap, fetch_results: HashMap>>>, images: HashMap>>>, - thread_summaries: HashMap>>>, + thread_summaries: HashMap>>>, text_thread_summaries: HashMap>>>, + directories: HashMap>>>, } impl MentionSet { @@ -1048,7 +1337,11 @@ impl MentionSet { self.images.insert(crease_id, task); } - fn insert_thread(&mut self, id: ThreadId, task: Shared>>) { + fn insert_thread( + &mut self, + id: acp::SessionId, + task: Shared>>, + ) { self.thread_summaries.insert(id, task); } @@ -1068,8 +1361,8 @@ impl MentionSet { pub fn contents( &self, - project: Entity, - thread_store: Entity, + project: &Entity, + prompt_store: Option<&Entity>, _window: &mut Window, cx: &mut App, ) -> Task>> { @@ -1081,7 +1374,6 @@ impl MentionSet { .map(|(&crease_id, uri)| { match uri { MentionUri::File { abs_path, .. } => { - // TODO directories let uri = uri.clone(); let abs_path = abs_path.to_path_buf(); @@ -1106,6 +1398,24 @@ impl MentionSet { anyhow::Ok((crease_id, Mention::Text { uri, content })) }) } + MentionUri::Directory { abs_path } => { + let Some(content) = self.directories.get(abs_path).cloned() else { + return Task::ready(Err(anyhow!("missing directory load task"))); + }; + let uri = uri.clone(); + cx.spawn(async move |_| { + Ok(( + crease_id, + Mention::Text { + uri, + content: content + .await + .map_err(|e| anyhow::anyhow!("{e}"))? + .to_string(), + }, + )) + }) + } MentionUri::Symbol { path, line_range, .. } @@ -1177,8 +1487,7 @@ impl MentionSet { }) } MentionUri::Rule { id: prompt_id, .. } => { - let Some(prompt_store) = thread_store.read(cx).prompt_store().clone() - else { + let Some(prompt_store) = prompt_store else { return Task::ready(Err(anyhow!("missing prompt store"))); }; let text_task = prompt_store.read(cx).load(*prompt_id, cx); @@ -1190,7 +1499,7 @@ impl MentionSet { }) } MentionUri::Fetch { url } => { - let Some(content) = self.fetch_results.get(&url).cloned() else { + let Some(content) = self.fetch_results.get(url).cloned() else { return Task::ready(Err(anyhow!("missing fetch result"))); }; let uri = uri.clone(); @@ -1230,12 +1539,150 @@ impl MentionSet { } } +struct SlashCommandSemanticsProvider { + range: Cell>, +} + +impl SemanticsProvider for SlashCommandSemanticsProvider { + fn hover( + &self, + buffer: &Entity, + position: text::Anchor, + cx: &mut App, + ) -> Option>> { + let snapshot = buffer.read(cx).snapshot(); + let offset = position.to_offset(&snapshot); + let (start, end) = self.range.get()?; + if !(start..end).contains(&offset) { + return None; + } + let range = snapshot.anchor_after(start)..snapshot.anchor_after(end); + Some(Task::ready(vec![project::Hover { + contents: vec![project::HoverBlock { + text: "Slash commands are not supported".into(), + kind: project::HoverBlockKind::PlainText, + }], + range: Some(range), + language: None, + }])) + } + + fn inline_values( + &self, + _buffer_handle: Entity, + _range: Range, + _cx: &mut App, + ) -> Option>>> { + None + } + + fn inlay_hints( + &self, + _buffer_handle: Entity, + _range: Range, + _cx: &mut App, + ) -> Option>>> { + None + } + + fn resolve_inlay_hint( + &self, + _hint: project::InlayHint, + _buffer_handle: Entity, + _server_id: lsp::LanguageServerId, + _cx: &mut App, + ) -> Option>> { + None + } + + fn supports_inlay_hints(&self, _buffer: &Entity, _cx: &mut App) -> bool { + false + } + + fn document_highlights( + &self, + _buffer: &Entity, + _position: text::Anchor, + _cx: &mut App, + ) -> Option>>> { + None + } + + fn definitions( + &self, + _buffer: &Entity, + _position: text::Anchor, + _kind: editor::GotoDefinitionKind, + _cx: &mut App, + ) -> Option>>> { + None + } + + fn range_for_rename( + &self, + _buffer: &Entity, + _position: text::Anchor, + _cx: &mut App, + ) -> Option>>>> { + None + } + + fn perform_rename( + &self, + _buffer: &Entity, + _position: text::Anchor, + _new_name: String, + _cx: &mut App, + ) -> Option>> { + None + } +} + +fn parse_slash_command(text: &str) -> Option<(usize, usize)> { + if let Some(remainder) = text.strip_prefix('/') { + let pos = remainder + .find(char::is_whitespace) + .unwrap_or(remainder.len()); + let command = &remainder[..pos]; + if !command.is_empty() && command.chars().all(char::is_alphanumeric) { + return Some((0, 1 + command.len())); + } + } + None +} + +pub struct MessageEditorAddon {} + +impl MessageEditorAddon { + pub fn new() -> Self { + Self {} + } +} + +impl Addon for MessageEditorAddon { + fn to_any(&self) -> &dyn std::any::Any { + self + } + + fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> { + Some(self) + } + + fn extend_key_context(&self, key_context: &mut KeyContext, cx: &App) { + let settings = agent_settings::AgentSettings::get_global(cx); + if settings.use_modifier_to_send { + key_context.add("use_modifier_to_send"); + } + } +} + #[cfg(test)] mod tests { use std::{ops::Range, path::Path, sync::Arc}; - use agent::{TextThreadStore, ThreadStore}; use agent_client_protocol as acp; + use agent2::HistoryStore; + use assistant_context::ContextStore; use editor::{AnchorRangeExt as _, Editor, EditorMode}; use fs::FakeFs; use futures::StreamExt as _; @@ -1247,7 +1694,7 @@ mod tests { use serde_json::json; use text::Point; use ui::{App, Context, IntoElement, Render, SharedString, Window}; - use util::path; + use util::{path, uri}; use workspace::{AppState, Item, Workspace}; use crate::acp::{ @@ -1266,16 +1713,18 @@ mod tests { let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); - let thread_store = cx.new(|cx| ThreadStore::fake(project.clone(), cx)); - let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx)); + let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx)); + let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); let message_editor = cx.update(|window, cx| { cx.new(|cx| { MessageEditor::new( workspace.downgrade(), project.clone(), - thread_store.clone(), - text_thread_store.clone(), + history_store.clone(), + None, + "Test", + false, EditorMode::AutoHeight { min_lines: 1, max_lines: None, @@ -1462,8 +1911,8 @@ mod tests { opened_editors.push(buffer); } - let thread_store = cx.new(|cx| ThreadStore::fake(project.clone(), cx)); - let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx)); + let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx)); + let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| { let workspace_handle = cx.weak_entity(); @@ -1471,8 +1920,10 @@ mod tests { MessageEditor::new( workspace_handle, project.clone(), - thread_store.clone(), - text_thread_store.clone(), + history_store.clone(), + None, + "Test", + false, EditorMode::AutoHeight { max_lines: None, min_lines: 1, @@ -1553,23 +2004,19 @@ mod tests { editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx); }); + let url_one = uri!("file:///dir/a/one.txt"); editor.update(&mut cx, |editor, cx| { - assert_eq!(editor.text(cx), "Lorem [@one.txt](file:///dir/a/one.txt) "); + let text = editor.text(cx); + assert_eq!(text, format!("Lorem [@one.txt]({url_one}) ")); assert!(!editor.has_visible_completions_menu()); - assert_eq!( - fold_ranges(editor, cx), - vec![Point::new(0, 6)..Point::new(0, 39)] - ); + assert_eq!(fold_ranges(editor, cx).len(), 1); }); let contents = message_editor .update_in(&mut cx, |message_editor, window, cx| { - message_editor.mention_set().contents( - project.clone(), - thread_store.clone(), - window, - cx, - ) + message_editor + .mention_set() + .contents(&project, None, window, cx) }) .await .unwrap() @@ -1580,47 +2027,35 @@ mod tests { contents, [Mention::Text { content: "1".into(), - uri: "file:///dir/a/one.txt".parse().unwrap() + uri: url_one.parse().unwrap() }] ); cx.simulate_input(" "); editor.update(&mut cx, |editor, cx| { - assert_eq!(editor.text(cx), "Lorem [@one.txt](file:///dir/a/one.txt) "); + let text = editor.text(cx); + assert_eq!(text, format!("Lorem [@one.txt]({url_one}) ")); assert!(!editor.has_visible_completions_menu()); - assert_eq!( - fold_ranges(editor, cx), - vec![Point::new(0, 6)..Point::new(0, 39)] - ); + assert_eq!(fold_ranges(editor, cx).len(), 1); }); cx.simulate_input("Ipsum "); editor.update(&mut cx, |editor, cx| { - assert_eq!( - editor.text(cx), - "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum ", - ); + let text = editor.text(cx); + assert_eq!(text, format!("Lorem [@one.txt]({url_one}) Ipsum "),); assert!(!editor.has_visible_completions_menu()); - assert_eq!( - fold_ranges(editor, cx), - vec![Point::new(0, 6)..Point::new(0, 39)] - ); + assert_eq!(fold_ranges(editor, cx).len(), 1); }); cx.simulate_input("@file "); editor.update(&mut cx, |editor, cx| { - assert_eq!( - editor.text(cx), - "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum @file ", - ); + let text = editor.text(cx); + assert_eq!(text, format!("Lorem [@one.txt]({url_one}) Ipsum @file "),); assert!(editor.has_visible_completions_menu()); - assert_eq!( - fold_ranges(editor, cx), - vec![Point::new(0, 6)..Point::new(0, 39)] - ); + assert_eq!(fold_ranges(editor, cx).len(), 1); }); editor.update_in(&mut cx, |editor, window, cx| { @@ -1631,12 +2066,9 @@ mod tests { let contents = message_editor .update_in(&mut cx, |message_editor, window, cx| { - message_editor.mention_set().contents( - project.clone(), - thread_store.clone(), - window, - cx, - ) + message_editor + .mention_set() + .contents(&project, None, window, cx) }) .await .unwrap() @@ -1644,28 +2076,23 @@ mod tests { .collect::>(); assert_eq!(contents.len(), 2); + let url_eight = uri!("file:///dir/b/eight.txt"); pretty_assertions::assert_eq!( contents[1], Mention::Text { content: "8".to_string(), - uri: "file:///dir/b/eight.txt".parse().unwrap(), + uri: url_eight.parse().unwrap(), } ); editor.update(&mut cx, |editor, cx| { - assert_eq!( - editor.text(cx), - "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum [@eight.txt](file:///dir/b/eight.txt) " - ); - assert!(!editor.has_visible_completions_menu()); - assert_eq!( - fold_ranges(editor, cx), - vec![ - Point::new(0, 6)..Point::new(0, 39), - Point::new(0, 47)..Point::new(0, 84) - ] - ); - }); + assert_eq!( + editor.text(cx), + format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) ") + ); + assert!(!editor.has_visible_completions_menu()); + assert_eq!(fold_ranges(editor, cx).len(), 2); + }); let plain_text_language = Arc::new(language::Language::new( language::LanguageConfig { @@ -1711,7 +2138,7 @@ mod tests { let fake_language_server = fake_language_servers.next().await.unwrap(); fake_language_server.set_request_handler::( - |_, _| async move { + move |_, _| async move { Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![ #[allow(deprecated)] lsp::SymbolInformation { @@ -1735,18 +2162,13 @@ mod tests { cx.simulate_input("@symbol "); editor.update(&mut cx, |editor, cx| { - assert_eq!( - editor.text(cx), - "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum [@eight.txt](file:///dir/b/eight.txt) @symbol " - ); - assert!(editor.has_visible_completions_menu()); - assert_eq!( - current_completion_labels(editor), - &[ - "MySymbol", - ] - ); - }); + assert_eq!( + editor.text(cx), + format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) @symbol ") + ); + assert!(editor.has_visible_completions_menu()); + assert_eq!(current_completion_labels(editor), &["MySymbol"]); + }); editor.update_in(&mut cx, |editor, window, cx| { editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx); @@ -1756,7 +2178,7 @@ mod tests { .update_in(&mut cx, |message_editor, window, cx| { message_editor .mention_set() - .contents(project.clone(), thread_store, window, cx) + .contents(&project, None, window, cx) }) .await .unwrap() @@ -1768,18 +2190,16 @@ mod tests { contents[2], Mention::Text { content: "1".into(), - uri: "file:///dir/a/one.txt?symbol=MySymbol#L1:1" - .parse() - .unwrap(), + uri: format!("{url_one}?symbol=MySymbol#L1:1").parse().unwrap(), } ); cx.run_until_parked(); - editor.read_with(&mut cx, |editor, cx| { + editor.read_with(&cx, |editor, cx| { assert_eq!( editor.text(cx), - "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum [@eight.txt](file:///dir/b/eight.txt) [@MySymbol](file:///dir/a/one.txt?symbol=MySymbol#L1:1) " + format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ") ); }); } diff --git a/crates/agent_ui/src/acp/model_selector.rs b/crates/agent_ui/src/acp/model_selector.rs index 563afee65f..77c88c461d 100644 --- a/crates/agent_ui/src/acp/model_selector.rs +++ b/crates/agent_ui/src/acp/model_selector.rs @@ -330,7 +330,7 @@ async fn fuzzy_search( .collect::>(); let mut matches = match_strings( &candidates, - &query, + query, false, true, 100, diff --git a/crates/agent_ui/src/acp/thread_history.rs b/crates/agent_ui/src/acp/thread_history.rs new file mode 100644 index 0000000000..4dfa5e8d4f --- /dev/null +++ b/crates/agent_ui/src/acp/thread_history.rs @@ -0,0 +1,721 @@ +use crate::RemoveSelectedThread; +use agent2::{HistoryEntry, HistoryStore}; +use chrono::{Datelike as _, Local, NaiveDate, TimeDelta}; +use editor::{Editor, EditorEvent}; +use fuzzy::{StringMatch, StringMatchCandidate}; +use gpui::{ + App, Empty, Entity, EventEmitter, FocusHandle, Focusable, ScrollStrategy, Task, + UniformListScrollHandle, Window, uniform_list, +}; +use std::{fmt::Display, ops::Range, sync::Arc}; +use time::{OffsetDateTime, UtcOffset}; +use ui::{ + HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tooltip, WithScrollbar, + prelude::*, +}; +use util::ResultExt; + +pub struct AcpThreadHistory { + pub(crate) history_store: Entity, + scroll_handle: UniformListScrollHandle, + selected_index: usize, + hovered_index: Option, + search_editor: Entity, + all_entries: Arc>, + // When the search is empty, we display date separators between history entries + // This vector contains an enum of either a separator or an actual entry + separated_items: Vec, + // Maps entry indexes to list item indexes + separated_item_indexes: Vec, + _separated_items_task: Option>, + search_state: SearchState, + local_timezone: UtcOffset, + _subscriptions: Vec, +} + +enum SearchState { + Empty, + Searching { + query: SharedString, + _task: Task<()>, + }, + Searched { + query: SharedString, + matches: Vec, + }, +} + +enum ListItemType { + BucketSeparator(TimeBucket), + Entry { + index: usize, + format: EntryTimeFormat, + }, +} + +pub enum ThreadHistoryEvent { + Open(HistoryEntry), +} + +impl EventEmitter for AcpThreadHistory {} + +impl AcpThreadHistory { + pub(crate) fn new( + history_store: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let search_editor = cx.new(|cx| { + let mut editor = Editor::single_line(window, cx); + editor.set_placeholder_text("Search threads...", cx); + editor + }); + + let search_editor_subscription = + cx.subscribe(&search_editor, |this, search_editor, event, cx| { + if let EditorEvent::BufferEdited = event { + let query = search_editor.read(cx).text(cx); + this.search(query.into(), cx); + } + }); + + let history_store_subscription = cx.observe(&history_store, |this, _, cx| { + this.update_all_entries(cx); + }); + + let scroll_handle = UniformListScrollHandle::default(); + + let mut this = Self { + history_store, + scroll_handle, + selected_index: 0, + hovered_index: None, + search_state: SearchState::Empty, + all_entries: Default::default(), + separated_items: Default::default(), + separated_item_indexes: Default::default(), + search_editor, + local_timezone: UtcOffset::from_whole_seconds( + chrono::Local::now().offset().local_minus_utc(), + ) + .unwrap(), + _subscriptions: vec![search_editor_subscription, history_store_subscription], + _separated_items_task: None, + }; + this.update_all_entries(cx); + this + } + + fn update_all_entries(&mut self, cx: &mut Context) { + let new_entries: Arc> = self + .history_store + .update(cx, |store, cx| store.entries(cx)) + .into(); + + self._separated_items_task.take(); + + let mut items = Vec::with_capacity(new_entries.len() + 1); + let mut indexes = Vec::with_capacity(new_entries.len() + 1); + + let bg_task = cx.background_spawn(async move { + let mut bucket = None; + let today = Local::now().naive_local().date(); + + for (index, entry) in new_entries.iter().enumerate() { + let entry_date = entry + .updated_at() + .with_timezone(&Local) + .naive_local() + .date(); + let entry_bucket = TimeBucket::from_dates(today, entry_date); + + if Some(entry_bucket) != bucket { + bucket = Some(entry_bucket); + items.push(ListItemType::BucketSeparator(entry_bucket)); + } + + indexes.push(items.len() as u32); + items.push(ListItemType::Entry { + index, + format: entry_bucket.into(), + }); + } + (new_entries, items, indexes) + }); + + let task = cx.spawn(async move |this, cx| { + let (new_entries, items, indexes) = bg_task.await; + this.update(cx, |this, cx| { + let previously_selected_entry = + this.all_entries.get(this.selected_index).map(|e| e.id()); + + this.all_entries = new_entries; + this.separated_items = items; + this.separated_item_indexes = indexes; + + match &this.search_state { + SearchState::Empty => { + if this.selected_index >= this.all_entries.len() { + this.set_selected_entry_index( + this.all_entries.len().saturating_sub(1), + cx, + ); + } else if let Some(prev_id) = previously_selected_entry + && let Some(new_ix) = this + .all_entries + .iter() + .position(|probe| probe.id() == prev_id) + { + this.set_selected_entry_index(new_ix, cx); + } + } + SearchState::Searching { query, .. } | SearchState::Searched { query, .. } => { + this.search(query.clone(), cx); + } + } + + cx.notify(); + }) + .log_err(); + }); + self._separated_items_task = Some(task); + } + + fn search(&mut self, query: SharedString, cx: &mut Context) { + if query.is_empty() { + self.search_state = SearchState::Empty; + cx.notify(); + return; + } + + let all_entries = self.all_entries.clone(); + + let fuzzy_search_task = cx.background_spawn({ + let query = query.clone(); + let executor = cx.background_executor().clone(); + async move { + let mut candidates = Vec::with_capacity(all_entries.len()); + + for (idx, entry) in all_entries.iter().enumerate() { + candidates.push(StringMatchCandidate::new(idx, entry.title())); + } + + const MAX_MATCHES: usize = 100; + + fuzzy::match_strings( + &candidates, + &query, + false, + true, + MAX_MATCHES, + &Default::default(), + executor, + ) + .await + } + }); + + let task = cx.spawn({ + let query = query.clone(); + async move |this, cx| { + let matches = fuzzy_search_task.await; + + this.update(cx, |this, cx| { + let SearchState::Searching { + query: current_query, + _task, + } = &this.search_state + else { + return; + }; + + if &query == current_query { + this.search_state = SearchState::Searched { + query: query.clone(), + matches, + }; + + this.set_selected_entry_index(0, cx); + cx.notify(); + }; + }) + .log_err(); + } + }); + + self.search_state = SearchState::Searching { query, _task: task }; + cx.notify(); + } + + fn matched_count(&self) -> usize { + match &self.search_state { + SearchState::Empty => self.all_entries.len(), + SearchState::Searching { .. } => 0, + SearchState::Searched { matches, .. } => matches.len(), + } + } + + fn list_item_count(&self) -> usize { + match &self.search_state { + SearchState::Empty => self.separated_items.len(), + SearchState::Searching { .. } => 0, + SearchState::Searched { matches, .. } => matches.len(), + } + } + + fn search_produced_no_matches(&self) -> bool { + match &self.search_state { + SearchState::Empty => false, + SearchState::Searching { .. } => false, + SearchState::Searched { matches, .. } => matches.is_empty(), + } + } + + fn get_match(&self, ix: usize) -> Option<&HistoryEntry> { + match &self.search_state { + SearchState::Empty => self.all_entries.get(ix), + SearchState::Searching { .. } => None, + SearchState::Searched { matches, .. } => matches + .get(ix) + .and_then(|m| self.all_entries.get(m.candidate_id)), + } + } + + pub fn select_previous( + &mut self, + _: &menu::SelectPrevious, + _window: &mut Window, + cx: &mut Context, + ) { + let count = self.matched_count(); + if count > 0 { + if self.selected_index == 0 { + self.set_selected_entry_index(count - 1, cx); + } else { + self.set_selected_entry_index(self.selected_index - 1, cx); + } + } + } + + pub fn select_next( + &mut self, + _: &menu::SelectNext, + _window: &mut Window, + cx: &mut Context, + ) { + let count = self.matched_count(); + if count > 0 { + if self.selected_index == count - 1 { + self.set_selected_entry_index(0, cx); + } else { + self.set_selected_entry_index(self.selected_index + 1, cx); + } + } + } + + fn select_first( + &mut self, + _: &menu::SelectFirst, + _window: &mut Window, + cx: &mut Context, + ) { + let count = self.matched_count(); + if count > 0 { + self.set_selected_entry_index(0, cx); + } + } + + fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context) { + let count = self.matched_count(); + if count > 0 { + self.set_selected_entry_index(count - 1, cx); + } + } + + fn set_selected_entry_index(&mut self, entry_index: usize, cx: &mut Context) { + self.selected_index = entry_index; + + let scroll_ix = match self.search_state { + SearchState::Empty | SearchState::Searching { .. } => self + .separated_item_indexes + .get(entry_index) + .map(|ix| *ix as usize) + .unwrap_or(entry_index + 1), + SearchState::Searched { .. } => entry_index, + }; + + self.scroll_handle + .scroll_to_item(scroll_ix, ScrollStrategy::Top); + + cx.notify(); + } + + fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context) { + self.confirm_entry(self.selected_index, cx); + } + + fn confirm_entry(&mut self, ix: usize, cx: &mut Context) { + let Some(entry) = self.get_match(ix) else { + return; + }; + cx.emit(ThreadHistoryEvent::Open(entry.clone())); + } + + fn remove_selected_thread( + &mut self, + _: &RemoveSelectedThread, + _window: &mut Window, + cx: &mut Context, + ) { + self.remove_thread(self.selected_index, cx) + } + + fn remove_thread(&mut self, ix: usize, cx: &mut Context) { + let Some(entry) = self.get_match(ix) else { + return; + }; + + let task = match entry { + HistoryEntry::AcpThread(thread) => self + .history_store + .update(cx, |this, cx| this.delete_thread(thread.id.clone(), cx)), + HistoryEntry::TextThread(context) => self.history_store.update(cx, |this, cx| { + this.delete_text_thread(context.path.clone(), cx) + }), + }; + task.detach_and_log_err(cx); + } + + fn list_items( + &mut self, + range: Range, + _window: &mut Window, + cx: &mut Context, + ) -> Vec { + match &self.search_state { + SearchState::Empty => self + .separated_items + .get(range) + .iter() + .flat_map(|items| { + items + .iter() + .map(|item| self.render_list_item(item, vec![], cx)) + }) + .collect(), + SearchState::Searched { matches, .. } => matches[range] + .iter() + .filter_map(|m| { + let entry = self.all_entries.get(m.candidate_id)?; + Some(self.render_history_entry( + entry, + EntryTimeFormat::DateAndTime, + m.candidate_id, + m.positions.clone(), + cx, + )) + }) + .collect(), + SearchState::Searching { .. } => { + vec![] + } + } + } + + fn render_list_item( + &self, + item: &ListItemType, + highlight_positions: Vec, + cx: &Context, + ) -> AnyElement { + match item { + ListItemType::Entry { index, format } => match self.all_entries.get(*index) { + Some(entry) => self + .render_history_entry(entry, *format, *index, highlight_positions, cx) + .into_any(), + None => Empty.into_any_element(), + }, + ListItemType::BucketSeparator(bucket) => div() + .px(DynamicSpacing::Base06.rems(cx)) + .pt_2() + .pb_1() + .child( + Label::new(bucket.to_string()) + .size(LabelSize::XSmall) + .color(Color::Muted), + ) + .into_any_element(), + } + } + + fn render_history_entry( + &self, + entry: &HistoryEntry, + format: EntryTimeFormat, + list_entry_ix: usize, + highlight_positions: Vec, + cx: &Context, + ) -> AnyElement { + let selected = list_entry_ix == self.selected_index; + let hovered = Some(list_entry_ix) == self.hovered_index; + let timestamp = entry.updated_at().timestamp(); + let thread_timestamp = format.format_timestamp(timestamp, self.local_timezone); + + h_flex() + .w_full() + .pb_1() + .child( + ListItem::new(list_entry_ix) + .rounded() + .toggle_state(selected) + .spacing(ListItemSpacing::Sparse) + .start_slot( + h_flex() + .w_full() + .gap_2() + .justify_between() + .child( + HighlightedLabel::new(entry.title(), highlight_positions) + .size(LabelSize::Small) + .truncate(), + ) + .child( + Label::new(thread_timestamp) + .color(Color::Muted) + .size(LabelSize::XSmall), + ), + ) + .on_hover(cx.listener(move |this, is_hovered, _window, cx| { + if *is_hovered { + this.hovered_index = Some(list_entry_ix); + } else if this.hovered_index == Some(list_entry_ix) { + this.hovered_index = None; + } + + cx.notify(); + })) + .end_slot::(if hovered || selected { + Some( + IconButton::new("delete", IconName::Trash) + .shape(IconButtonShape::Square) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .tooltip(move |window, cx| { + Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx) + }) + .on_click(cx.listener(move |this, _, _, cx| { + this.remove_thread(list_entry_ix, cx) + })), + ) + } else { + None + }) + .on_click( + cx.listener(move |this, _, _, cx| this.confirm_entry(list_entry_ix, cx)), + ), + ) + .into_any_element() + } +} + +impl Focusable for AcpThreadHistory { + fn focus_handle(&self, cx: &App) -> FocusHandle { + self.search_editor.focus_handle(cx) + } +} + +impl Render for AcpThreadHistory { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .key_context("ThreadHistory") + .size_full() + .on_action(cx.listener(Self::select_previous)) + .on_action(cx.listener(Self::select_next)) + .on_action(cx.listener(Self::select_first)) + .on_action(cx.listener(Self::select_last)) + .on_action(cx.listener(Self::confirm)) + .on_action(cx.listener(Self::remove_selected_thread)) + .when(!self.all_entries.is_empty(), |parent| { + parent.child( + h_flex() + .h(px(41.)) // Match the toolbar perfectly + .w_full() + .py_1() + .px_2() + .gap_2() + .justify_between() + .border_b_1() + .border_color(cx.theme().colors().border) + .child( + Icon::new(IconName::MagnifyingGlass) + .color(Color::Muted) + .size(IconSize::Small), + ) + .child(self.search_editor.clone()), + ) + }) + .child({ + let view = v_flex() + .id("list-container") + .relative() + .overflow_hidden() + .flex_grow(); + + if self.all_entries.is_empty() { + view.justify_center() + .child( + h_flex().w_full().justify_center().child( + Label::new("You don't have any past threads yet.") + .size(LabelSize::Small), + ), + ) + } else if self.search_produced_no_matches() { + view.justify_center().child( + h_flex().w_full().justify_center().child( + Label::new("No threads match your search.").size(LabelSize::Small), + ), + ) + } else { + view.pr_5().child( + uniform_list( + "thread-history", + self.list_item_count(), + cx.processor(|this, range: Range, window, cx| { + this.list_items(range, window, cx) + }), + ) + .p_1() + .track_scroll(self.scroll_handle.clone()) + .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx) + .flex_grow(), + ) + } + }) + } +} + +#[derive(Clone, Copy)] +pub enum EntryTimeFormat { + DateAndTime, + TimeOnly, +} + +impl EntryTimeFormat { + fn format_timestamp(&self, timestamp: i64, timezone: UtcOffset) -> String { + let timestamp = OffsetDateTime::from_unix_timestamp(timestamp).unwrap(); + + match self { + EntryTimeFormat::DateAndTime => time_format::format_localized_timestamp( + timestamp, + OffsetDateTime::now_utc(), + timezone, + time_format::TimestampFormat::EnhancedAbsolute, + ), + EntryTimeFormat::TimeOnly => time_format::format_time(timestamp), + } + } +} + +impl From for EntryTimeFormat { + fn from(bucket: TimeBucket) -> Self { + match bucket { + TimeBucket::Today => EntryTimeFormat::TimeOnly, + TimeBucket::Yesterday => EntryTimeFormat::TimeOnly, + TimeBucket::ThisWeek => EntryTimeFormat::DateAndTime, + TimeBucket::PastWeek => EntryTimeFormat::DateAndTime, + TimeBucket::All => EntryTimeFormat::DateAndTime, + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +enum TimeBucket { + Today, + Yesterday, + ThisWeek, + PastWeek, + All, +} + +impl TimeBucket { + fn from_dates(reference: NaiveDate, date: NaiveDate) -> Self { + if date == reference { + return TimeBucket::Today; + } + + if date == reference - TimeDelta::days(1) { + return TimeBucket::Yesterday; + } + + let week = date.iso_week(); + + if reference.iso_week() == week { + return TimeBucket::ThisWeek; + } + + let last_week = (reference - TimeDelta::days(7)).iso_week(); + + if week == last_week { + return TimeBucket::PastWeek; + } + + TimeBucket::All + } +} + +impl Display for TimeBucket { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TimeBucket::Today => write!(f, "Today"), + TimeBucket::Yesterday => write!(f, "Yesterday"), + TimeBucket::ThisWeek => write!(f, "This Week"), + TimeBucket::PastWeek => write!(f, "Past Week"), + TimeBucket::All => write!(f, "All"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::NaiveDate; + + #[test] + fn test_time_bucket_from_dates() { + let today = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(); + + let date = today; + assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::Today); + + let date = NaiveDate::from_ymd_opt(2023, 1, 14).unwrap(); + assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::Yesterday); + + let date = NaiveDate::from_ymd_opt(2023, 1, 13).unwrap(); + assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::ThisWeek); + + let date = NaiveDate::from_ymd_opt(2023, 1, 11).unwrap(); + assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::ThisWeek); + + let date = NaiveDate::from_ymd_opt(2023, 1, 8).unwrap(); + assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::PastWeek); + + let date = NaiveDate::from_ymd_opt(2023, 1, 5).unwrap(); + assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::PastWeek); + + // All: not in this week or last week + let date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(); + assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::All); + + // Test year boundary cases + let new_year = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(); + + let date = NaiveDate::from_ymd_opt(2022, 12, 31).unwrap(); + assert_eq!( + TimeBucket::from_dates(new_year, date), + TimeBucket::Yesterday + ); + + let date = NaiveDate::from_ymd_opt(2022, 12, 28).unwrap(); + assert_eq!(TimeBucket::from_dates(new_year, date), TimeBucket::ThisWeek); + } +} diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index f2901ed1c2..2f85ed416e 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -1,13 +1,14 @@ use acp_thread::{ AcpThread, AcpThreadEvent, AgentThreadEntry, AssistantMessage, AssistantMessageChunk, - LoadError, MentionUri, ThreadStatus, ToolCall, ToolCallContent, ToolCallStatus, UserMessageId, + AuthRequired, LoadError, MentionUri, RetryStatus, ThreadStatus, ToolCall, ToolCallContent, + ToolCallStatus, UserMessageId, }; use acp_thread::{AgentConnection, Plan}; use action_log::ActionLog; -use agent::{TextThreadStore, ThreadStore}; use agent_client_protocol::{self as acp}; -use agent_servers::AgentServer; +use agent_servers::{AgentServer, ClaudeCode}; use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting}; +use agent2::{DbThreadMetadata, HistoryEntryId, HistoryStore}; use anyhow::bail; use audio::{Audio, Sound}; use buffer_diff::BufferDiff; @@ -18,20 +19,24 @@ use editor::{Editor, EditorMode, MultiBuffer, PathKey, SelectionEffects}; use file_icons::FileIcons; use fs::Fs; use gpui::{ - Action, Animation, AnimationExt, App, BorderStyle, ClickEvent, ClipboardItem, EdgesRefinement, - Empty, Entity, FocusHandle, Focusable, Hsla, Length, ListOffset, ListState, PlatformDisplay, - SharedString, StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, - Transformation, UnderlineStyle, WeakEntity, Window, WindowHandle, div, linear_color_stop, - linear_gradient, list, percentage, point, prelude::*, pulsating_between, + Action, Animation, AnimationExt, AnyView, App, BorderStyle, ClickEvent, ClipboardItem, + EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length, ListOffset, + ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task, TextStyle, + TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, Window, WindowHandle, div, + ease_in_out, linear_color_stop, linear_gradient, list, percentage, point, prelude::*, + pulsating_between, }; use language::Buffer; + +use language_model::LanguageModelRegistry; use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle}; -use project::Project; -use prompt_store::PromptId; +use project::{Project, ProjectEntryId}; +use prompt_store::{PromptId, PromptStore}; use rope::Point; use settings::{Settings as _, SettingsStore}; use std::sync::Arc; -use std::{collections::BTreeMap, process::ExitStatus, rc::Rc, time::Duration}; +use std::time::Instant; +use std::{collections::BTreeMap, rc::Rc, time::Duration}; use text::Anchor; use theme::ThemeSettings; use ui::{ @@ -96,7 +101,7 @@ impl ProfileProvider for Entity { fn profiles_supported(&self, cx: &App) -> bool { self.read(cx) .model() - .map_or(false, |model| model.supports_tools()) + .is_some_and(|model| model.supports_tools()) } } @@ -105,12 +110,14 @@ pub struct AcpThreadView { workspace: WeakEntity, project: Entity, thread_state: ThreadState, + history_store: Entity, entry_view_state: Entity, message_editor: Entity, model_selector: Option>, profile_selector: Option>, notifications: Vec>, notification_subscriptions: HashMap, Vec>, + thread_retry_status: Option, thread_error: Option, list_state: ListState, auth_task: Option>, @@ -136,28 +143,32 @@ enum ThreadState { LoadError(LoadError), Unauthenticated { connection: Rc, - }, - ServerExited { - status: ExitStatus, + description: Option>, + configuration_view: Option, + _subscription: Option, }, } impl AcpThreadView { pub fn new( agent: Rc, + resume_thread: Option, workspace: WeakEntity, project: Entity, - thread_store: Entity, - text_thread_store: Entity, + history_store: Entity, + prompt_store: Option>, window: &mut Window, cx: &mut Context, ) -> Self { + let prevent_slash_commands = agent.clone().downcast::().is_some(); let message_editor = cx.new(|cx| { MessageEditor::new( workspace.clone(), project.clone(), - thread_store.clone(), - text_thread_store.clone(), + history_store.clone(), + prompt_store.clone(), + "Message the agent — @ to include context", + prevent_slash_commands, editor::EditorMode::AutoHeight { min_lines: MIN_EDITOR_LINES, max_lines: Some(MAX_EDITOR_LINES), @@ -173,8 +184,9 @@ impl AcpThreadView { EntryViewState::new( workspace.clone(), project.clone(), - thread_store.clone(), - text_thread_store.clone(), + history_store.clone(), + prompt_store.clone(), + prevent_slash_commands, ) }); @@ -189,13 +201,14 @@ impl AcpThreadView { workspace: workspace.clone(), project: project.clone(), entry_view_state, - thread_state: Self::initial_state(agent, workspace, project, window, cx), + thread_state: Self::initial_state(agent, resume_thread, workspace, project, window, cx), message_editor, model_selector: None, profile_selector: None, notifications: Vec::new(), notification_subscriptions: HashMap::default(), list_state: list_state, + thread_retry_status: None, thread_error: None, auth_task: None, expanded_tool_calls: HashSet::default(), @@ -205,6 +218,7 @@ impl AcpThreadView { plan_expanded: false, editor_expanded: false, terminal_expanded: true, + history_store, _subscriptions: subscriptions, _cancel_task: None, } @@ -212,6 +226,7 @@ impl AcpThreadView { fn initial_state( agent: Rc, + resume_thread: Option, workspace: WeakEntity, project: Entity, window: &mut Window, @@ -238,45 +253,41 @@ impl AcpThreadView { } }; - // this.update_in(cx, |_this, _window, cx| { - // let status = connection.exit_status(cx); - // cx.spawn(async move |this, cx| { - // let status = status.await.ok(); - // this.update(cx, |this, cx| { - // this.thread_state = ThreadState::ServerExited { status }; - // cx.notify(); - // }) - // .ok(); - // }) - // .detach(); - // }) - // .ok(); - - let Some(result) = cx - .update(|_, cx| { + let result = if let Some(native_agent) = connection + .clone() + .downcast::() + && let Some(resume) = resume_thread.clone() + { + cx.update(|_, cx| { + native_agent + .0 + .update(cx, |agent, cx| agent.open_thread(resume.id, cx)) + }) + .log_err() + } else { + cx.update(|_, cx| { connection .clone() .new_thread(project.clone(), &root_dir, cx) }) .log_err() - else { + }; + + let Some(result) = result else { return; }; let result = match result.await { - Err(e) => { - let mut cx = cx.clone(); - if e.is::() { - this.update(&mut cx, |this, cx| { - this.thread_state = ThreadState::Unauthenticated { connection }; - cx.notify(); + Err(e) => match e.downcast::() { + Ok(err) => { + cx.update(|window, cx| { + Self::handle_auth_required(this, err, agent, connection, window, cx) }) - .ok(); + .log_err(); return; - } else { - Err(e) } - } + Err(err) => Err(err), + }, Ok(thread) => Ok(thread), }; @@ -290,8 +301,22 @@ impl AcpThreadView { let action_log_subscription = cx.observe(&action_log, |_, _, cx| cx.notify()); - this.list_state - .splice(0..0, thread.read(cx).entries().len()); + let count = thread.read(cx).entries().len(); + this.list_state.splice(0..0, count); + this.entry_view_state.update(cx, |view_state, cx| { + for ix in 0..count { + view_state.sync_entry(ix, &thread, window, cx); + } + }); + + if let Some(resume) = resume_thread { + this.history_store.update(cx, |history, cx| { + history.push_recently_opened_entry( + HistoryEntryId::AcpThread(resume.id), + cx, + ); + }); + } AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx); @@ -342,6 +367,69 @@ impl AcpThreadView { ThreadState::Loading { _task: load_task } } + fn handle_auth_required( + this: WeakEntity, + err: AuthRequired, + agent: Rc, + connection: Rc, + window: &mut Window, + cx: &mut App, + ) { + let agent_name = agent.name(); + let (configuration_view, subscription) = if let Some(provider_id) = err.provider_id { + let registry = LanguageModelRegistry::global(cx); + + let sub = window.subscribe(®istry, cx, { + let provider_id = provider_id.clone(); + let this = this.clone(); + move |_, ev, window, cx| { + if let language_model::Event::ProviderStateChanged(updated_provider_id) = &ev + && &provider_id == updated_provider_id + { + this.update(cx, |this, cx| { + this.thread_state = Self::initial_state( + agent.clone(), + None, + this.workspace.clone(), + this.project.clone(), + window, + cx, + ); + cx.notify(); + }) + .ok(); + } + } + }); + + let view = registry.read(cx).provider(&provider_id).map(|provider| { + provider.configuration_view( + language_model::ConfigurationViewTargetAgent::Other(agent_name), + window, + cx, + ) + }); + + (view, Some(sub)) + } else { + (None, None) + }; + + this.update(cx, |this, cx| { + this.thread_state = ThreadState::Unauthenticated { + connection, + configuration_view, + description: err + .description + .clone() + .map(|desc| cx.new(|cx| Markdown::new(desc.into(), None, None, cx))), + _subscription: subscription, + }; + cx.notify(); + }) + .ok(); + } + fn handle_load_error(&mut self, err: anyhow::Error, cx: &mut Context) { if let Some(load_err) = err.downcast_ref::() { self.thread_state = ThreadState::LoadError(load_err.clone()); @@ -356,8 +444,7 @@ impl AcpThreadView { ThreadState::Ready { thread, .. } => Some(thread), ThreadState::Unauthenticated { .. } | ThreadState::Loading { .. } - | ThreadState::LoadError(..) - | ThreadState::ServerExited { .. } => None, + | ThreadState::LoadError { .. } => None, } } @@ -366,13 +453,13 @@ impl AcpThreadView { ThreadState::Ready { thread, .. } => thread.read(cx).title(), ThreadState::Loading { .. } => "Loading…".into(), ThreadState::LoadError(_) => "Failed to load".into(), - ThreadState::Unauthenticated { .. } => "Not authenticated".into(), - ThreadState::ServerExited { .. } => "Server exited unexpectedly".into(), + ThreadState::Unauthenticated { .. } => "Authentication Required".into(), } } pub fn cancel_generation(&mut self, cx: &mut Context) { self.thread_error.take(); + self.thread_retry_status.take(); if let Some(thread) = self.thread() { self._cancel_task = Some(thread.update(cx, |thread, cx| thread.cancel(cx))); @@ -424,7 +511,9 @@ impl AcpThreadView { match event { MessageEditorEvent::Send => self.send(window, cx), MessageEditorEvent::Cancel => self.cancel_generation(cx), - MessageEditorEvent::Focus => {} + MessageEditorEvent::Focus => { + self.cancel_editing(&Default::default(), window, cx); + } } } @@ -469,12 +558,47 @@ impl AcpThreadView { } fn send(&mut self, window: &mut Window, cx: &mut Context) { + let Some(thread) = self.thread() else { return }; + self.history_store.update(cx, |history, cx| { + history.push_recently_opened_entry( + HistoryEntryId::AcpThread(thread.read(cx).session_id().clone()), + cx, + ); + }); + + if thread.read(cx).status() != ThreadStatus::Idle { + self.stop_current_and_send_new_message(window, cx); + return; + } + let contents = self .message_editor .update(cx, |message_editor, cx| message_editor.contents(window, cx)); self.send_impl(contents, window, cx) } + fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context) { + let Some(thread) = self.thread().cloned() else { + return; + }; + + let cancelled = thread.update(cx, |thread, cx| thread.cancel(cx)); + + let contents = self + .message_editor + .update(cx, |message_editor, cx| message_editor.contents(window, cx)); + + cx.spawn_in(window, async move |this, cx| { + cancelled.await; + + this.update_in(cx, |this, window, cx| { + this.send_impl(contents, window, cx); + }) + .ok(); + }) + .detach(); + } + fn send_impl( &mut self, contents: Task>>, @@ -521,25 +645,24 @@ impl AcpThreadView { return; }; - if let Some(index) = self.editing_message.take() { - if let Some(editor) = self + if let Some(index) = self.editing_message.take() + && let Some(editor) = self .entry_view_state .read(cx) .entry(index) .and_then(|e| e.message_editor()) .cloned() - { - editor.update(cx, |editor, cx| { - if let Some(user_message) = thread - .read(cx) - .entries() - .get(index) - .and_then(|e| e.user_message()) - { - editor.set_message(user_message.chunks.clone(), window, cx); - } - }) - } + { + editor.update(cx, |editor, cx| { + if let Some(user_message) = thread + .read(cx) + .entries() + .get(index) + .and_then(|e| e.user_message()) + { + editor.set_message(user_message.chunks.clone(), window, cx); + } + }) }; self.focus_handle(cx).focus(window); cx.notify(); @@ -596,10 +719,42 @@ impl AcpThreadView { }; diff.update(cx, |diff, cx| { - diff.move_to_path(PathKey::for_buffer(&buffer, cx), window, cx) + diff.move_to_path(PathKey::for_buffer(buffer, cx), window, cx) }) } + fn handle_open_rules(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context) { + let Some(thread) = self.as_native_thread(cx) else { + return; + }; + let project_context = thread.read(cx).project_context().read(cx); + + let project_entry_ids = project_context + .worktrees + .iter() + .flat_map(|worktree| worktree.rules_file.as_ref()) + .map(|rules_file| ProjectEntryId::from_usize(rules_file.project_entry_id)) + .collect::>(); + + self.workspace + .update(cx, move |workspace, cx| { + // TODO: Open a multibuffer instead? In some cases this doesn't make the set of rules + // files clear. For example, if rules file 1 is already open but rules file 2 is not, + // this would open and focus rules file 2 in a tab that is not next to rules file 1. + let project = workspace.project().read(cx); + let project_paths = project_entry_ids + .into_iter() + .flat_map(|entry_id| project.path_for_entry(entry_id, cx)) + .collect::>(); + for project_path in project_paths { + workspace + .open_path(project_path, None, true, window, cx) + .detach_and_log_err(cx); + } + }) + .ok(); + } + fn handle_thread_error(&mut self, error: anyhow::Error, cx: &mut Context) { self.thread_error = Some(ThreadError::from_err(error)); cx.notify(); @@ -622,13 +777,13 @@ impl AcpThreadView { let len = thread.read(cx).entries().len(); let index = len - 1; self.entry_view_state.update(cx, |view_state, cx| { - view_state.sync_entry(index, &thread, window, cx) + view_state.sync_entry(index, thread, window, cx) }); self.list_state.splice(index..index, 1); } AcpThreadEvent::EntryUpdated(index) => { self.entry_view_state.update(cx, |view_state, cx| { - view_state.sync_entry(*index, &thread, window, cx) + view_state.sync_entry(*index, thread, window, cx) }); self.list_state.splice(*index..index + 1, 1); } @@ -640,7 +795,11 @@ impl AcpThreadView { AcpThreadEvent::ToolAuthorizationRequired => { self.notify_with_sound("Waiting for tool confirmation", IconName::Info, window, cx); } + AcpThreadEvent::Retry(retry) => { + self.thread_retry_status = Some(retry.clone()); + } AcpThreadEvent::Stopped => { + self.thread_retry_status.take(); let used_tools = thread.read(cx).used_tools_since_last_user_message(); self.notify_with_sound( if used_tools { @@ -654,6 +813,7 @@ impl AcpThreadView { ); } AcpThreadEvent::Error => { + self.thread_retry_status.take(); self.notify_with_sound( "Agent stopped due to an error", IconName::Warning, @@ -661,9 +821,11 @@ impl AcpThreadView { cx, ); } - AcpThreadEvent::ServerExited(status) => { - self.thread_state = ThreadState::ServerExited { status: *status }; + AcpThreadEvent::LoadError(error) => { + self.thread_retry_status.take(); + self.thread_state = ThreadState::LoadError(error.clone()); } + AcpThreadEvent::TitleUpdated | AcpThreadEvent::TokenUsageUpdated => {} } cx.notify(); } @@ -674,7 +836,7 @@ impl AcpThreadView { window: &mut Window, cx: &mut Context, ) { - let ThreadState::Unauthenticated { ref connection } = self.thread_state else { + let ThreadState::Unauthenticated { ref connection, .. } = self.thread_state else { return; }; @@ -692,6 +854,7 @@ impl AcpThreadView { } else { this.thread_state = Self::initial_state( agent, + None, this.workspace.clone(), project.clone(), window, @@ -740,44 +903,117 @@ impl AcpThreadView { cx: &Context, ) -> AnyElement { let primary = match &entry { - AgentThreadEntry::UserMessage(message) => div() - .id(("user_message", entry_ix)) - .py_4() - .px_2() - .children(message.id.clone().and_then(|message_id| { - message.checkpoint.as_ref()?.show.then(|| { - Button::new("restore-checkpoint", "Restore Checkpoint") - .icon(IconName::Undo) - .icon_size(IconSize::XSmall) - .icon_position(IconPosition::Start) - .label_size(LabelSize::XSmall) - .on_click(cx.listener(move |this, _, _window, cx| { - this.rewind(&message_id, cx); - })) - }) - })) - .child( - v_flex() - .p_3() - .gap_1p5() - .rounded_lg() - .shadow_md() - .bg(cx.theme().colors().editor_background) - .border_1() - .border_color(cx.theme().colors().border) - .text_xs() - .children( - self.entry_view_state - .read(cx) - .entry(entry_ix) - .and_then(|entry| entry.message_editor()) - .map(|editor| { - self.render_sent_message_editor(entry_ix, editor, cx) - .into_any_element() - }), - ), - ) - .into_any(), + AgentThreadEntry::UserMessage(message) => { + let Some(editor) = self + .entry_view_state + .read(cx) + .entry(entry_ix) + .and_then(|entry| entry.message_editor()) + .cloned() + else { + return Empty.into_any_element(); + }; + + let editing = self.editing_message == Some(entry_ix); + let editor_focus = editor.focus_handle(cx).is_focused(window); + let focus_border = cx.theme().colors().border_focused; + + let rules_item = if entry_ix == 0 { + self.render_rules_item(cx) + } else { + None + }; + + v_flex() + .id(("user_message", entry_ix)) + .pt_2() + .pb_4() + .px_2() + .gap_1p5() + .w_full() + .children(rules_item) + .children(message.id.clone().and_then(|message_id| { + message.checkpoint.as_ref()?.show.then(|| { + h_flex() + .gap_2() + .child(Divider::horizontal()) + .child( + Button::new("restore-checkpoint", "Restore Checkpoint") + .icon(IconName::Undo) + .icon_size(IconSize::XSmall) + .icon_position(IconPosition::Start) + .label_size(LabelSize::XSmall) + .icon_color(Color::Muted) + .color(Color::Muted) + .on_click(cx.listener(move |this, _, _window, cx| { + this.rewind(&message_id, cx); + })) + ) + .child(Divider::horizontal()) + }) + })) + .child( + div() + .relative() + .child( + div() + .py_3() + .px_2() + .rounded_lg() + .shadow_md() + .bg(cx.theme().colors().editor_background) + .border_1() + .when(editing && !editor_focus, |this| this.border_dashed()) + .border_color(cx.theme().colors().border) + .map(|this|{ + if editor_focus { + this.border_color(focus_border) + } else { + this.hover(|s| s.border_color(focus_border.opacity(0.8))) + } + }) + .text_xs() + .child(editor.clone().into_any_element()), + ) + .when(editor_focus, |this| + this.child( + h_flex() + .absolute() + .top_neg_3p5() + .right_3() + .gap_1() + .rounded_sm() + .border_1() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().editor_background) + .overflow_hidden() + .child( + IconButton::new("cancel", IconName::Close) + .icon_color(Color::Error) + .icon_size(IconSize::XSmall) + .on_click(cx.listener(Self::cancel_editing)) + ) + .child( + IconButton::new("regenerate", IconName::Return) + .icon_color(Color::Muted) + .icon_size(IconSize::XSmall) + .tooltip(Tooltip::text( + "Editing will restart the thread from this point." + )) + .on_click(cx.listener({ + let editor = editor.clone(); + move |this, _, window, cx| { + this.regenerate( + entry_ix, &editor, window, cx, + ); + } + })), + ) + ) + ), + ) + .into_any() + } AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) => { let style = default_markdown_style(false, window, cx); let message_body = v_flex() @@ -880,7 +1116,7 @@ impl AcpThreadView { } fn tool_card_border_color(&self, cx: &Context) -> Hsla { - cx.theme().colors().border.opacity(0.6) + cx.theme().colors().border.opacity(0.8) } fn tool_name_font_size(&self) -> Rems { @@ -1079,23 +1315,14 @@ impl AcpThreadView { tool_call.status, ToolCallStatus::WaitingForConfirmation { .. } ); - let is_edit = matches!(tool_call.kind, acp::ToolKind::Edit); - let has_diff = tool_call - .content - .iter() - .any(|content| matches!(content, ToolCallContent::Diff { .. })); - let has_nonempty_diff = tool_call.content.iter().any(|content| match content { - ToolCallContent::Diff(diff) => diff.read(cx).has_revealed_range(cx), - _ => false, - }); - let use_card_layout = needs_confirmation || is_edit || has_diff; + let is_edit = + matches!(tool_call.kind, acp::ToolKind::Edit) || tool_call.diffs().next().is_some(); + let use_card_layout = needs_confirmation || is_edit; let is_collapsible = !tool_call.content.is_empty() && !use_card_layout; - let is_open = tool_call.content.is_empty() - || needs_confirmation - || has_nonempty_diff - || self.expanded_tool_calls.contains(&tool_call.id); + let is_open = + needs_confirmation || is_edit || self.expanded_tool_calls.contains(&tool_call.id); let gradient_overlay = |color: Hsla| { div() @@ -1116,41 +1343,49 @@ impl AcpThreadView { cx.theme().colors().panel_background }; - let tool_output_display = match &tool_call.status { - ToolCallStatus::WaitingForConfirmation { options, .. } => v_flex() - .w_full() - .children(tool_call.content.iter().map(|content| { - div() - .child( - self.render_tool_call_content(entry_ix, content, tool_call, window, cx), - ) - .into_any_element() - })) - .child(self.render_permission_buttons( - options, - entry_ix, - tool_call.id.clone(), - tool_call.content.is_empty(), - cx, - )), - ToolCallStatus::Pending - | ToolCallStatus::InProgress - | ToolCallStatus::Completed - | ToolCallStatus::Failed - | ToolCallStatus::Canceled => { - v_flex() + let tool_output_display = if is_open { + match &tool_call.status { + ToolCallStatus::WaitingForConfirmation { options, .. } => { + v_flex() + .w_full() + .children(tool_call.content.iter().map(|content| { + div() + .child(self.render_tool_call_content( + entry_ix, content, tool_call, window, cx, + )) + .into_any_element() + })) + .child(self.render_permission_buttons( + options, + entry_ix, + tool_call.id.clone(), + tool_call.content.is_empty(), + cx, + )) + .into_any() + } + ToolCallStatus::Pending | ToolCallStatus::InProgress + if is_edit && tool_call.content.is_empty() => + { + self.render_diff_loading(cx).into_any() + } + ToolCallStatus::Pending + | ToolCallStatus::InProgress + | ToolCallStatus::Completed + | ToolCallStatus::Failed + | ToolCallStatus::Canceled => v_flex() .w_full() .children(tool_call.content.iter().map(|content| { - div() - .child( - self.render_tool_call_content( - entry_ix, content, tool_call, window, cx, - ), - ) - .into_any_element() + div().child( + self.render_tool_call_content(entry_ix, content, tool_call, window, cx), + ) })) + .into_any(), + ToolCallStatus::Rejected => Empty.into_any(), } - ToolCallStatus::Rejected => v_flex().size_0(), + .into() + } else { + None }; v_flex() @@ -1170,9 +1405,13 @@ impl AcpThreadView { .map(|this| { if use_card_layout { this.pl_2() - .pr_1() + .pr_1p5() .py_1() .rounded_t_md() + .when(is_open, |this| { + this.border_b_1() + .border_color(self.tool_card_border_color(cx)) + }) .bg(self.tool_card_header_bg(cx)) } else { this.opacity(0.8).hover(|style| style.opacity(1.)) @@ -1183,6 +1422,7 @@ impl AcpThreadView { .group(&card_header_id) .relative() .w_full() + .min_h_6() .text_size(self.tool_name_font_size()) .child(self.render_tool_call_icon( card_header_id, @@ -1236,11 +1476,7 @@ impl AcpThreadView { .overflow_x_scroll() .child(self.render_markdown( tool_call.label.clone(), - default_markdown_style( - needs_confirmation || is_edit || has_diff, - window, - cx, - ), + default_markdown_style(false, window, cx), )), ) .child(gradient_overlay(gradient_color)) @@ -1260,7 +1496,7 @@ impl AcpThreadView { ) .children(status_icon), ) - .when(is_open, |this| this.child(tool_output_display)) + .children(tool_output_display) } fn render_tool_call_content( @@ -1281,7 +1517,7 @@ impl AcpThreadView { Empty.into_any_element() } } - ToolCallContent::Diff(diff) => self.render_diff_editor(entry_ix, &diff, cx), + ToolCallContent::Diff(diff) => self.render_diff_editor(entry_ix, diff, tool_call, cx), ToolCallContent::Terminal(terminal) => { self.render_terminal_tool_call(entry_ix, terminal, tool_call, window, cx) } @@ -1425,21 +1661,69 @@ impl AcpThreadView { }))) } + fn render_diff_loading(&self, cx: &Context) -> AnyElement { + let bar = |n: u64, width_class: &str| { + let bg_color = cx.theme().colors().element_active; + let base = h_flex().h_1().rounded_full(); + + let modified = match width_class { + "w_4_5" => base.w_3_4(), + "w_1_4" => base.w_1_4(), + "w_2_4" => base.w_2_4(), + "w_3_5" => base.w_3_5(), + "w_2_5" => base.w_2_5(), + _ => base.w_1_2(), + }; + + modified.with_animation( + ElementId::Integer(n), + Animation::new(Duration::from_secs(2)).repeat(), + move |tab, delta| { + let delta = (delta - 0.15 * n as f32) / 0.7; + let delta = 1.0 - (0.5 - delta).abs() * 2.; + let delta = ease_in_out(delta.clamp(0., 1.)); + let delta = 0.1 + 0.9 * delta; + + tab.bg(bg_color.opacity(delta)) + }, + ) + }; + + v_flex() + .p_3() + .gap_1() + .rounded_b_md() + .bg(cx.theme().colors().editor_background) + .child(bar(0, "w_4_5")) + .child(bar(1, "w_1_4")) + .child(bar(2, "w_2_4")) + .child(bar(3, "w_3_5")) + .child(bar(4, "w_2_5")) + .into_any_element() + } + fn render_diff_editor( &self, entry_ix: usize, diff: &Entity, + tool_call: &ToolCall, cx: &Context, ) -> AnyElement { + let tool_progress = matches!( + &tool_call.status, + ToolCallStatus::InProgress | ToolCallStatus::Pending + ); + v_flex() .h_full() - .border_t_1() - .border_color(self.tool_card_border_color(cx)) .child( if let Some(entry) = self.entry_view_state.read(cx).entry(entry_ix) - && let Some(editor) = entry.editor_for_diff(&diff) + && let Some(editor) = entry.editor_for_diff(diff) + && diff.read(cx).has_revealed_range(cx) { editor.clone().into_any_element() + } else if tool_progress { + self.render_diff_loading(cx) } else { Empty.into_any() }, @@ -1637,7 +1921,7 @@ impl AcpThreadView { .entry_view_state .read(cx) .entry(entry_ix) - .and_then(|entry| entry.terminal(&terminal)); + .and_then(|entry| entry.terminal(terminal)); let show_output = self.terminal_expanded && terminal_view.is_some(); v_flex() @@ -1704,15 +1988,139 @@ impl AcpThreadView { .justify_center() .child(div().opacity(0.3).child(logo)) .child( - h_flex().absolute().right_1().bottom_0().child( - Icon::new(IconName::XCircle) - .color(Color::Error) - .size(IconSize::Small), - ), + h_flex() + .absolute() + .right_1() + .bottom_0() + .child(Icon::new(IconName::XCircleFilled).color(Color::Error)), ) .into_any_element() } + fn render_rules_item(&self, cx: &Context) -> Option { + let project_context = self + .as_native_thread(cx)? + .read(cx) + .project_context() + .read(cx); + + let user_rules_text = if project_context.user_rules.is_empty() { + None + } else if project_context.user_rules.len() == 1 { + let user_rules = &project_context.user_rules[0]; + + match user_rules.title.as_ref() { + Some(title) => Some(format!("Using \"{title}\" user rule")), + None => Some("Using user rule".into()), + } + } else { + Some(format!( + "Using {} user rules", + project_context.user_rules.len() + )) + }; + + let first_user_rules_id = project_context + .user_rules + .first() + .map(|user_rules| user_rules.uuid.0); + + let rules_files = project_context + .worktrees + .iter() + .filter_map(|worktree| worktree.rules_file.as_ref()) + .collect::>(); + + let rules_file_text = match rules_files.as_slice() { + &[] => None, + &[rules_file] => Some(format!( + "Using project {:?} file", + rules_file.path_in_worktree + )), + rules_files => Some(format!("Using {} project rules files", rules_files.len())), + }; + + if user_rules_text.is_none() && rules_file_text.is_none() { + return None; + } + + Some( + v_flex() + .px_2p5() + .gap_1() + .when_some(user_rules_text, |parent, user_rules_text| { + parent.child( + h_flex() + .group("user-rules") + .id("user-rules") + .w_full() + .child( + Icon::new(IconName::Reader) + .size(IconSize::XSmall) + .color(Color::Disabled), + ) + .child( + Label::new(user_rules_text) + .size(LabelSize::XSmall) + .color(Color::Muted) + .truncate() + .buffer_font(cx) + .ml_1p5() + .mr_0p5(), + ) + .child( + IconButton::new("open-prompt-library", IconName::ArrowUpRight) + .shape(ui::IconButtonShape::Square) + .icon_size(IconSize::XSmall) + .icon_color(Color::Ignored) + .visible_on_hover("user-rules") + // TODO: Figure out a way to pass focus handle here so we can display the `OpenRulesLibrary` keybinding + .tooltip(Tooltip::text("View User Rules")), + ) + .on_click(move |_event, window, cx| { + window.dispatch_action( + Box::new(OpenRulesLibrary { + prompt_to_select: first_user_rules_id, + }), + cx, + ) + }), + ) + }) + .when_some(rules_file_text, |parent, rules_file_text| { + parent.child( + h_flex() + .group("project-rules") + .id("project-rules") + .w_full() + .child( + Icon::new(IconName::Reader) + .size(IconSize::XSmall) + .color(Color::Disabled), + ) + .child( + Label::new(rules_file_text) + .size(LabelSize::XSmall) + .color(Color::Muted) + .buffer_font(cx) + .ml_1p5() + .mr_0p5(), + ) + .child( + IconButton::new("open-rule", IconName::ArrowUpRight) + .shape(ui::IconButtonShape::Square) + .icon_size(IconSize::XSmall) + .icon_color(Color::Ignored) + .visible_on_hover("project-rules") + .tooltip(Tooltip::text("View Project Rules")), + ) + .on_click(cx.listener(Self::handle_open_rules)), + ) + }) + .into_any(), + ) + } + fn render_empty_state(&self, cx: &App) -> AnyElement { let loading = matches!(&self.thread_state, ThreadState::Loading { .. }); @@ -1761,41 +2169,51 @@ impl AcpThreadView { .into_any() } - fn render_pending_auth_state(&self) -> AnyElement { + fn render_auth_required_state( + &self, + connection: &Rc, + description: Option<&Entity>, + configuration_view: Option<&AnyView>, + window: &mut Window, + cx: &Context, + ) -> Div { v_flex() + .p_2() + .gap_2() + .flex_1() .items_center() .justify_center() - .child(self.render_error_agent_logo()) - .child( - h_flex() - .mt_4() - .mb_1() - .justify_center() - .child(Headline::new("Not Authenticated").size(HeadlineSize::Medium)), - ) - .into_any() - } - - fn render_server_exited(&self, status: ExitStatus, _cx: &Context) -> AnyElement { - v_flex() - .items_center() - .justify_center() - .child(self.render_error_agent_logo()) .child( v_flex() - .mt_4() - .mb_2() - .gap_0p5() - .text_center() .items_center() - .child(Headline::new("Server exited unexpectedly").size(HeadlineSize::Medium)) - .child( - Label::new(format!("Exit status: {}", status.code().unwrap_or(-127))) - .size(LabelSize::Small) - .color(Color::Muted), - ), + .justify_center() + .child(self.render_error_agent_logo()) + .child(h_flex().mt_4().mb_1().justify_center().child( + Headline::new(self.agent.empty_state_headline()).size(HeadlineSize::Medium), + )) + .into_any(), ) - .into_any_element() + .children(description.map(|desc| { + div().text_ui(cx).text_center().child( + self.render_markdown(desc.clone(), default_markdown_style(false, window, cx)), + ) + })) + .children( + configuration_view + .cloned() + .map(|view| div().px_4().w_full().max_w_128().child(view)), + ) + .child(h_flex().mt_1p5().justify_center().children( + connection.auth_methods().iter().map(|method| { + Button::new(SharedString::from(method.id.0.clone()), method.name.clone()) + .on_click({ + let method_id = method.id.clone(); + cx.listener(move |this, _, window, cx| { + this.authenticate(method_id.clone(), window, cx) + }) + }) + }), + )) } fn render_load_error(&self, e: &LoadError, cx: &Context) -> AnyElement { @@ -1826,39 +2244,102 @@ impl AcpThreadView { { let upgrade_message = upgrade_message.clone(); let upgrade_command = upgrade_command.clone(); - container = container.child(Button::new("upgrade", upgrade_message).on_click( - cx.listener(move |this, _, window, cx| { - this.workspace - .update(cx, |workspace, cx| { - let project = workspace.project().read(cx); - let cwd = project.first_project_directory(cx); - let shell = project.terminal_settings(&cwd, cx).shell.clone(); - let spawn_in_terminal = task::SpawnInTerminal { - id: task::TaskId("install".to_string()), - full_label: upgrade_command.clone(), - label: upgrade_command.clone(), - command: Some(upgrade_command.clone()), - args: Vec::new(), - command_label: upgrade_command.clone(), - cwd, - env: Default::default(), - use_new_terminal: true, - allow_concurrent_runs: true, - reveal: Default::default(), - reveal_target: Default::default(), - hide: Default::default(), - shell, - show_summary: true, - show_command: true, - show_rerun: false, - }; - workspace - .spawn_in_terminal(spawn_in_terminal, window, cx) - .detach(); + container = container.child( + Button::new("upgrade", upgrade_message) + .tooltip(Tooltip::text(upgrade_command.clone())) + .on_click(cx.listener(move |this, _, window, cx| { + let task = this + .workspace + .update(cx, |workspace, cx| { + let project = workspace.project().read(cx); + let cwd = project.first_project_directory(cx); + let shell = project.terminal_settings(&cwd, cx).shell.clone(); + let spawn_in_terminal = task::SpawnInTerminal { + id: task::TaskId("upgrade".to_string()), + full_label: upgrade_command.clone(), + label: upgrade_command.clone(), + command: Some(upgrade_command.clone()), + args: Vec::new(), + command_label: upgrade_command.clone(), + cwd, + env: Default::default(), + use_new_terminal: true, + allow_concurrent_runs: true, + reveal: Default::default(), + reveal_target: Default::default(), + hide: Default::default(), + shell, + show_summary: true, + show_command: true, + show_rerun: false, + }; + workspace.spawn_in_terminal(spawn_in_terminal, window, cx) + }) + .ok(); + let Some(task) = task else { return }; + cx.spawn_in(window, async move |this, cx| { + if let Some(Ok(_)) = task.await { + this.update_in(cx, |this, window, cx| { + this.reset(window, cx); + }) + .ok(); + } }) - .ok(); - }), - )); + .detach() + })), + ); + } else if let LoadError::NotInstalled { + install_message, + install_command, + .. + } = e + { + let install_message = install_message.clone(); + let install_command = install_command.clone(); + container = container.child( + Button::new("install", install_message) + .tooltip(Tooltip::text(install_command.clone())) + .on_click(cx.listener(move |this, _, window, cx| { + let task = this + .workspace + .update(cx, |workspace, cx| { + let project = workspace.project().read(cx); + let cwd = project.first_project_directory(cx); + let shell = project.terminal_settings(&cwd, cx).shell.clone(); + let spawn_in_terminal = task::SpawnInTerminal { + id: task::TaskId("install".to_string()), + full_label: install_command.clone(), + label: install_command.clone(), + command: Some(install_command.clone()), + args: Vec::new(), + command_label: install_command.clone(), + cwd, + env: Default::default(), + use_new_terminal: true, + allow_concurrent_runs: true, + reveal: Default::default(), + reveal_target: Default::default(), + hide: Default::default(), + shell, + show_summary: true, + show_command: true, + show_rerun: false, + }; + workspace.spawn_in_terminal(spawn_in_terminal, window, cx) + }) + .ok(); + let Some(task) = task else { return }; + cx.spawn_in(window, async move |this, cx| { + if let Some(Ok(_)) = task.await { + this.update_in(cx, |this, window, cx| { + this.reset(window, cx); + }) + .ok(); + } + }) + .detach() + })), + ); } container.into_any() @@ -2214,7 +2695,7 @@ impl AcpThreadView { ) -> Div { let editor_bg_color = cx.theme().colors().editor_background; - v_flex().children(changed_buffers.into_iter().enumerate().flat_map( + v_flex().children(changed_buffers.iter().enumerate().flat_map( |(index, (buffer, _diff))| { let file = buffer.read(cx).file()?; let path = file.path(); @@ -2240,7 +2721,7 @@ impl AcpThreadView { .buffer_font(cx) }); - let file_icon = FileIcons::get_icon(&path, cx) + let file_icon = FileIcons::get_icon(path, cx) .map(Icon::from_path) .map(|icon| icon.color(Color::Muted).size(IconSize::Small)) .unwrap_or_else(|| { @@ -2424,16 +2905,17 @@ impl AcpThreadView { .child( h_flex() .flex_none() + .flex_wrap() .justify_between() .child( h_flex() - .gap_1() .child(self.render_follow_toggle(cx)) .children(self.render_burn_mode_toggle(cx)), ) .child( h_flex() .gap_1() + .children(self.render_token_usage(cx)) .children(self.profile_selector.clone()) .children(self.model_selector.clone()) .child(self.render_send_button(cx)), @@ -2456,6 +2938,48 @@ impl AcpThreadView { .thread(acp_thread.session_id(), cx) } + fn render_token_usage(&self, cx: &mut Context) -> Option
{ + let thread = self.thread()?.read(cx); + let usage = thread.token_usage()?; + let is_generating = thread.status() != ThreadStatus::Idle; + + let used = crate::text_thread_editor::humanize_token_count(usage.used_tokens); + let max = crate::text_thread_editor::humanize_token_count(usage.max_tokens); + + Some( + h_flex() + .flex_shrink_0() + .gap_0p5() + .mr_1p5() + .child( + Label::new(used) + .size(LabelSize::Small) + .color(Color::Muted) + .map(|label| { + if is_generating { + label + .with_animation( + "used-tokens-label", + Animation::new(Duration::from_secs(2)) + .repeat() + .with_easing(pulsating_between(0.6, 1.)), + |label, delta| label.alpha(delta), + ) + .into_any() + } else { + label.into_any_element() + } + }), + ) + .child( + Label::new("/") + .size(LabelSize::Small) + .color(Color::Custom(cx.theme().colors().text_muted.opacity(0.5))), + ) + .child(Label::new(max).size(LabelSize::Small).color(Color::Muted)), + ) + } + fn toggle_burn_mode( &mut self, _: &ToggleBurnMode, @@ -2466,12 +2990,15 @@ impl AcpThreadView { return; }; - thread.update(cx, |thread, _cx| { + thread.update(cx, |thread, cx| { let current_mode = thread.completion_mode(); - thread.set_completion_mode(match current_mode { - CompletionMode::Burn => CompletionMode::Normal, - CompletionMode::Normal => CompletionMode::Burn, - }); + thread.set_completion_mode( + match current_mode { + CompletionMode::Burn => CompletionMode::Normal, + CompletionMode::Normal => CompletionMode::Burn, + }, + cx, + ); }); } @@ -2480,7 +3007,7 @@ impl AcpThreadView { if thread .model() - .map_or(true, |model| !model.supports_burn_mode()) + .is_none_or(|model| !model.supports_burn_mode()) { return None; } @@ -2510,110 +3037,13 @@ impl AcpThreadView { ) } - fn render_sent_message_editor( - &self, - entry_ix: usize, - editor: &Entity, - cx: &Context, - ) -> Div { - v_flex().w_full().gap_2().child(editor.clone()).when( - self.editing_message == Some(entry_ix), - |el| { - el.child( - h_flex() - .gap_1() - .child( - Icon::new(IconName::Warning) - .color(Color::Warning) - .size(IconSize::XSmall), - ) - .child( - Label::new("Editing will restart the thread from this point.") - .color(Color::Muted) - .size(LabelSize::XSmall), - ) - .child(self.render_sent_message_editor_buttons(entry_ix, editor, cx)), - ) - }, - ) - } - - fn render_sent_message_editor_buttons( - &self, - entry_ix: usize, - editor: &Entity, - cx: &Context, - ) -> Div { - h_flex() - .gap_0p5() - .flex_1() - .justify_end() - .child( - IconButton::new("cancel-edit-message", IconName::Close) - .shape(ui::IconButtonShape::Square) - .icon_color(Color::Error) - .icon_size(IconSize::Small) - .tooltip({ - let focus_handle = editor.focus_handle(cx); - move |window, cx| { - Tooltip::for_action_in( - "Cancel Edit", - &menu::Cancel, - &focus_handle, - window, - cx, - ) - } - }) - .on_click(cx.listener(Self::cancel_editing)), - ) - .child( - IconButton::new("confirm-edit-message", IconName::Return) - .disabled(editor.read(cx).is_empty(cx)) - .shape(ui::IconButtonShape::Square) - .icon_color(Color::Muted) - .icon_size(IconSize::Small) - .tooltip({ - let focus_handle = editor.focus_handle(cx); - move |window, cx| { - Tooltip::for_action_in( - "Regenerate", - &menu::Confirm, - &focus_handle, - window, - cx, - ) - } - }) - .on_click(cx.listener({ - let editor = editor.clone(); - move |this, _, window, cx| { - this.regenerate(entry_ix, &editor, window, cx); - } - })), - ) - } - fn render_send_button(&self, cx: &mut Context) -> AnyElement { - if self.thread().map_or(true, |thread| { - thread.read(cx).status() == ThreadStatus::Idle - }) { - let is_editor_empty = self.message_editor.read(cx).is_empty(cx); - IconButton::new("send-message", IconName::Send) - .icon_color(Color::Accent) - .style(ButtonStyle::Filled) - .disabled(self.thread().is_none() || is_editor_empty) - .when(!is_editor_empty, |button| { - button.tooltip(move |window, cx| Tooltip::for_action("Send", &Chat, window, cx)) - }) - .when(is_editor_empty, |button| { - button.tooltip(Tooltip::text("Type a message to submit")) - }) - .on_click(cx.listener(|this, _, window, cx| { - this.send(window, cx); - })) - .into_any_element() - } else { + let is_editor_empty = self.message_editor.read(cx).is_empty(cx); + let is_generating = self + .thread() + .is_some_and(|thread| thread.read(cx).status() != ThreadStatus::Idle); + + if is_generating && is_editor_empty { IconButton::new("stop-generation", IconName::Stop) .icon_color(Color::Error) .style(ButtonStyle::Tinted(ui::TintColor::Error)) @@ -2622,6 +3052,29 @@ impl AcpThreadView { }) .on_click(cx.listener(|this, _event, _, cx| this.cancel_generation(cx))) .into_any_element() + } else { + let send_btn_tooltip = if is_editor_empty && !is_generating { + "Type to Send" + } else if is_generating { + "Stop and Send Message" + } else { + "Send" + }; + + IconButton::new("send-message", IconName::Send) + .style(ButtonStyle::Filled) + .map(|this| { + if is_editor_empty && !is_generating { + this.disabled(true).icon_color(Color::Muted) + } else { + this.icon_color(Color::Accent) + } + }) + .tooltip(move |window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, window, cx)) + .on_click(cx.listener(|this, _, window, cx| { + this.send(window, cx); + })) + .into_any_element() } } @@ -2684,25 +3137,30 @@ impl AcpThreadView { if let Some(mention) = MentionUri::parse(&url).log_err() { workspace.update(cx, |workspace, cx| match mention { - MentionUri::File { abs_path, .. } => { + MentionUri::File { abs_path } => { let project = workspace.project(); - let Some((path, entry)) = project.update(cx, |project, cx| { + let Some(path) = + project.update(cx, |project, cx| project.find_project_path(abs_path, cx)) + else { + return; + }; + + workspace + .open_path(path, None, true, window, cx) + .detach_and_log_err(cx); + } + MentionUri::Directory { abs_path } => { + let project = workspace.project(); + let Some(entry) = project.update(cx, |project, cx| { let path = project.find_project_path(abs_path, cx)?; - let entry = project.entry_for_path(&path, cx)?; - Some((path, entry)) + project.entry_for_path(&path, cx) }) else { return; }; - if entry.is_dir() { - project.update(cx, |_, cx| { - cx.emit(project::Event::RevealInProjectPanel(entry.id)); - }); - } else { - workspace - .open_path(path, None, true, window, cx) - .detach_and_log_err(cx); - } + project.update(cx, |_, cx| { + cx.emit(project::Event::RevealInProjectPanel(entry.id)); + }); } MentionUri::Symbol { path, line_range, .. @@ -2739,12 +3197,18 @@ impl AcpThreadView { }) .detach_and_log_err(cx); } - MentionUri::Thread { id, .. } => { + MentionUri::Thread { id, name } => { if let Some(panel) = workspace.panel::(cx) { panel.update(cx, |panel, cx| { - panel - .open_thread_by_id(&id, window, cx) - .detach_and_log_err(cx) + panel.load_agent_thread( + DbThreadMetadata { + id, + title: name.into(), + updated_at: Default::default(), + }, + window, + cx, + ) }); } } @@ -2983,62 +3447,61 @@ impl AcpThreadView { }) }) .log_err() + && let Some(pop_up) = screen_window.entity(cx).log_err() { - if let Some(pop_up) = screen_window.entity(cx).log_err() { - self.notification_subscriptions - .entry(screen_window) - .or_insert_with(Vec::new) - .push(cx.subscribe_in(&pop_up, window, { - |this, _, event, window, cx| match event { - AgentNotificationEvent::Accepted => { - let handle = window.window_handle(); - cx.activate(true); + self.notification_subscriptions + .entry(screen_window) + .or_insert_with(Vec::new) + .push(cx.subscribe_in(&pop_up, window, { + |this, _, event, window, cx| match event { + AgentNotificationEvent::Accepted => { + let handle = window.window_handle(); + cx.activate(true); - let workspace_handle = this.workspace.clone(); + let workspace_handle = this.workspace.clone(); - // If there are multiple Zed windows, activate the correct one. - cx.defer(move |cx| { - handle - .update(cx, |_view, window, _cx| { - window.activate_window(); + // If there are multiple Zed windows, activate the correct one. + cx.defer(move |cx| { + handle + .update(cx, |_view, window, _cx| { + window.activate_window(); - if let Some(workspace) = workspace_handle.upgrade() { - workspace.update(_cx, |workspace, cx| { - workspace.focus_panel::(window, cx); - }); - } - }) - .log_err(); - }); + if let Some(workspace) = workspace_handle.upgrade() { + workspace.update(_cx, |workspace, cx| { + workspace.focus_panel::(window, cx); + }); + } + }) + .log_err(); + }); - this.dismiss_notifications(cx); - } - AgentNotificationEvent::Dismissed => { - this.dismiss_notifications(cx); - } + this.dismiss_notifications(cx); } - })); + AgentNotificationEvent::Dismissed => { + this.dismiss_notifications(cx); + } + } + })); - self.notifications.push(screen_window); + self.notifications.push(screen_window); - // If the user manually refocuses the original window, dismiss the popup. - self.notification_subscriptions - .entry(screen_window) - .or_insert_with(Vec::new) - .push({ - let pop_up_weak = pop_up.downgrade(); + // If the user manually refocuses the original window, dismiss the popup. + self.notification_subscriptions + .entry(screen_window) + .or_insert_with(Vec::new) + .push({ + let pop_up_weak = pop_up.downgrade(); - cx.observe_window_activation(window, move |_, window, cx| { - if window.is_window_active() { - if let Some(pop_up) = pop_up_weak.upgrade() { - pop_up.update(cx, |_, cx| { - cx.emit(AgentNotificationEvent::Dismissed); - }); - } - } - }) - }); - } + cx.observe_window_activation(window, move |_, window, cx| { + if window.is_window_active() + && let Some(pop_up) = pop_up_weak.upgrade() + { + pop_up.update(cx, |_, cx| { + cx.emit(AgentNotificationEvent::Dismissed); + }); + } + }) + }); } } @@ -3103,12 +3566,53 @@ impl AcpThreadView { cx: &mut Context, ) { self.message_editor.update(cx, |message_editor, cx| { - message_editor.insert_dragged_files(paths, window, cx); - drop(added_worktrees); + message_editor.insert_dragged_files(paths, added_worktrees, window, cx); }) } - fn render_thread_error(&self, window: &mut Window, cx: &mut Context<'_, Self>) -> Option
{ + fn render_thread_retry_status_callout( + &self, + _window: &mut Window, + _cx: &mut Context, + ) -> Option { + let state = self.thread_retry_status.as_ref()?; + + let next_attempt_in = state + .duration + .saturating_sub(Instant::now().saturating_duration_since(state.started_at)); + if next_attempt_in.is_zero() { + return None; + } + + let next_attempt_in_secs = next_attempt_in.as_secs() + 1; + + let retry_message = if state.max_attempts == 1 { + if next_attempt_in_secs == 1 { + "Retrying. Next attempt in 1 second.".to_string() + } else { + format!("Retrying. Next attempt in {next_attempt_in_secs} seconds.") + } + } else if next_attempt_in_secs == 1 { + format!( + "Retrying. Next attempt in 1 second (Attempt {} of {}).", + state.attempt, state.max_attempts, + ) + } else { + format!( + "Retrying. Next attempt in {next_attempt_in_secs} seconds (Attempt {} of {}).", + state.attempt, state.max_attempts, + ) + }; + + Some( + Callout::new() + .severity(Severity::Warning) + .title(state.last_error.clone()) + .description(retry_message), + ) + } + + fn render_thread_error(&self, window: &mut Window, cx: &mut Context) -> Option
{ let content = match self.thread_error.as_ref()? { ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx), ThreadError::PaymentRequired => self.render_payment_required_error(cx), @@ -3120,44 +3624,33 @@ impl AcpThreadView { } }; - Some( - div() - .border_t_1() - .border_color(cx.theme().colors().border) - .child(content), - ) + Some(div().child(content)) } fn render_any_thread_error(&self, error: SharedString, cx: &mut Context<'_, Self>) -> Callout { - let icon = Icon::new(IconName::XCircle) - .size(IconSize::Small) - .color(Color::Error); - Callout::new() - .icon(icon) + .severity(Severity::Error) .title("Error") .description(error.clone()) - .secondary_action(self.create_copy_button(error.to_string())) - .primary_action(self.dismiss_error_button(cx)) - .bg_color(self.error_callout_bg(cx)) + .actions_slot(self.create_copy_button(error.to_string())) + .dismiss_action(self.dismiss_error_button(cx)) } fn render_payment_required_error(&self, cx: &mut Context) -> Callout { const ERROR_MESSAGE: &str = "You reached your free usage limit. Upgrade to Zed Pro for more prompts."; - let icon = Icon::new(IconName::XCircle) - .size(IconSize::Small) - .color(Color::Error); - Callout::new() - .icon(icon) + .severity(Severity::Error) .title("Free Usage Exceeded") .description(ERROR_MESSAGE) - .tertiary_action(self.upgrade_button(cx)) - .secondary_action(self.create_copy_button(ERROR_MESSAGE)) - .primary_action(self.dismiss_error_button(cx)) - .bg_color(self.error_callout_bg(cx)) + .actions_slot( + h_flex() + .gap_0p5() + .child(self.upgrade_button(cx)) + .child(self.create_copy_button(ERROR_MESSAGE)), + ) + .dismiss_action(self.dismiss_error_button(cx)) } fn render_model_request_limit_reached_error( @@ -3172,18 +3665,17 @@ impl AcpThreadView { } }; - let icon = Icon::new(IconName::XCircle) - .size(IconSize::Small) - .color(Color::Error); - Callout::new() - .icon(icon) + .severity(Severity::Error) .title("Model Prompt Limit Reached") .description(error_message) - .tertiary_action(self.upgrade_button(cx)) - .secondary_action(self.create_copy_button(error_message)) - .primary_action(self.dismiss_error_button(cx)) - .bg_color(self.error_callout_bg(cx)) + .actions_slot( + h_flex() + .gap_0p5() + .child(self.upgrade_button(cx)) + .child(self.create_copy_button(error_message)), + ) + .dismiss_action(self.dismiss_error_button(cx)) } fn render_tool_use_limit_reached_error( @@ -3195,56 +3687,64 @@ impl AcpThreadView { let supports_burn_mode = thread .read(cx) .model() - .map_or(false, |model| model.supports_burn_mode()); + .is_some_and(|model| model.supports_burn_mode()); let focus_handle = self.focus_handle(cx); - let icon = Icon::new(IconName::Info) - .size(IconSize::Small) - .color(Color::Info); - Some( Callout::new() - .icon(icon) + .icon(IconName::Info) .title("Consecutive tool use limit reached.") - .when(supports_burn_mode, |this| { - this.secondary_action( - Button::new("continue-burn-mode", "Continue with Burn Mode") - .style(ButtonStyle::Filled) - .style(ButtonStyle::Tinted(ui::TintColor::Accent)) - .layer(ElevationIndex::ModalSurface) - .label_size(LabelSize::Small) - .key_binding( - KeyBinding::for_action_in( - &ContinueWithBurnMode, - &focus_handle, - window, - cx, - ) - .map(|kb| kb.size(rems_from_px(10.))), + .actions_slot( + h_flex() + .gap_0p5() + .when(supports_burn_mode, |this| { + this.child( + Button::new("continue-burn-mode", "Continue with Burn Mode") + .style(ButtonStyle::Filled) + .style(ButtonStyle::Tinted(ui::TintColor::Accent)) + .layer(ElevationIndex::ModalSurface) + .label_size(LabelSize::Small) + .key_binding( + KeyBinding::for_action_in( + &ContinueWithBurnMode, + &focus_handle, + window, + cx, + ) + .map(|kb| kb.size(rems_from_px(10.))), + ) + .tooltip(Tooltip::text( + "Enable Burn Mode for unlimited tool use.", + )) + .on_click({ + cx.listener(move |this, _, _window, cx| { + thread.update(cx, |thread, cx| { + thread + .set_completion_mode(CompletionMode::Burn, cx); + }); + this.resume_chat(cx); + }) + }), ) - .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use.")) - .on_click({ - cx.listener(move |this, _, _window, cx| { - thread.update(cx, |thread, _cx| { - thread.set_completion_mode(CompletionMode::Burn); - }); + }) + .child( + Button::new("continue-conversation", "Continue") + .layer(ElevationIndex::ModalSurface) + .label_size(LabelSize::Small) + .key_binding( + KeyBinding::for_action_in( + &ContinueThread, + &focus_handle, + window, + cx, + ) + .map(|kb| kb.size(rems_from_px(10.))), + ) + .on_click(cx.listener(|this, _, _window, cx| { this.resume_chat(cx); - }) - }), - ) - }) - .primary_action( - Button::new("continue-conversation", "Continue") - .layer(ElevationIndex::ModalSurface) - .label_size(LabelSize::Small) - .key_binding( - KeyBinding::for_action_in(&ContinueThread, &focus_handle, window, cx) - .map(|kb| kb.size(rems_from_px(10.))), - ) - .on_click(cx.listener(|this, _, _window, cx| { - this.resume_chat(cx); - })), + })), + ), ), ) } @@ -3286,8 +3786,16 @@ impl AcpThreadView { })) } - fn error_callout_bg(&self, cx: &Context) -> Hsla { - cx.theme().status().error.opacity(0.08) + fn reset(&mut self, window: &mut Window, cx: &mut Context) { + self.thread_state = Self::initial_state( + self.agent.clone(), + None, + self.workspace.clone(), + self.project.clone(), + window, + cx, + ); + cx.notify(); } } @@ -3308,45 +3816,31 @@ impl Render for AcpThreadView { .on_action(cx.listener(Self::toggle_burn_mode)) .bg(cx.theme().colors().panel_background) .child(match &self.thread_state { - ThreadState::Unauthenticated { connection } => v_flex() - .p_2() - .flex_1() - .items_center() - .justify_center() - .child(self.render_pending_auth_state()) - .child(h_flex().mt_1p5().justify_center().children( - connection.auth_methods().into_iter().map(|method| { - Button::new( - SharedString::from(method.id.0.clone()), - method.name.clone(), - ) - .on_click({ - let method_id = method.id.clone(); - cx.listener(move |this, _, window, cx| { - this.authenticate(method_id.clone(), window, cx) - }) - }) - }), - )) - .into_any_element(), + ThreadState::Unauthenticated { + connection, + description, + configuration_view, + .. + } => self + .render_auth_required_state( + connection, + description.as_ref(), + configuration_view.as_ref(), + window, + cx, + ) + .into_any(), ThreadState::Loading { .. } => v_flex() .flex_1() .child(self.render_empty_state(cx)) - .into_any_element(), + .into_any(), ThreadState::LoadError(e) => v_flex() .p_2() .flex_1() .items_center() .justify_center() .child(self.render_load_error(e, cx)) - .into_any_element(), - ThreadState::ServerExited { status } => v_flex() - .p_2() - .flex_1() - .items_center() - .justify_center() - .child(self.render_server_exited(*status, cx)) - .into_any_element(), + .into_any(), ThreadState::Ready { thread, .. } => { let thread_clone = thread.clone(); @@ -3397,6 +3891,7 @@ impl Render for AcpThreadView { } _ => this, }) + .children(self.render_thread_retry_status_callout(window, cx)) .children(self.render_thread_error(window, cx)) .child(self.render_message_editor(window, cx)) } @@ -3556,8 +4051,8 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { #[cfg(test)] pub(crate) mod tests { use acp_thread::StubAgentConnection; - use agent::{TextThreadStore, ThreadStore}; use agent_client_protocol::SessionId; + use assistant_context::ContextStore; use editor::EditorSettings; use fs::FakeFs; use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext}; @@ -3574,7 +4069,7 @@ pub(crate) mod tests { async fn test_drop(cx: &mut TestAppContext) { init_test(cx); - let (thread_view, _cx) = setup_thread_view(StubAgentServer::default(), cx).await; + let (thread_view, _cx) = setup_thread_view(StubAgentServer::default_response(), cx).await; let weak_view = thread_view.downgrade(); drop(thread_view); assert!(!weak_view.is_upgradable()); @@ -3584,7 +4079,7 @@ pub(crate) mod tests { async fn test_notification_for_stop_event(cx: &mut TestAppContext) { init_test(cx); - let (thread_view, cx) = setup_thread_view(StubAgentServer::default(), cx).await; + let (thread_view, cx) = setup_thread_view(StubAgentServer::default_response(), cx).await; let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); message_editor.update_in(cx, |editor, window, cx| { @@ -3691,19 +4186,20 @@ pub(crate) mod tests { let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); - let thread_store = - cx.update(|_window, cx| cx.new(|cx| ThreadStore::fake(project.clone(), cx))); - let text_thread_store = - cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx))); + let context_store = + cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx))); + let history_store = + cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx))); let thread_view = cx.update(|window, cx| { cx.new(|cx| { AcpThreadView::new( Rc::new(agent), + None, workspace.downgrade(), project, - thread_store.clone(), - text_thread_store.clone(), + history_store, + None, window, cx, ) @@ -3768,8 +4264,12 @@ pub(crate) mod tests { } impl StubAgentServer { - fn default() -> Self { - Self::new(StubAgentConnection::default()) + fn default_response() -> Self { + let conn = StubAgentConnection::new(); + conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk { + content: "Default response".into(), + }]); + Self::new(conn) } } @@ -3801,6 +4301,10 @@ pub(crate) mod tests { ) -> Task>> { Task::ready(Ok(Rc::new(self.connection.clone()))) } + + fn into_any(self: Rc) -> Rc { + self + } } #[derive(Clone)] @@ -3814,12 +4318,13 @@ pub(crate) mod tests { cx: &mut gpui::App, ) -> Task>> { Task::ready(Ok(cx.new(|cx| { + let action_log = cx.new(|_| ActionLog::new(project.clone())); AcpThread::new( "SaboteurAgentConnection", self, project, + action_log, SessionId("test".into()), - cx, ) }))) } @@ -3865,6 +4370,7 @@ pub(crate) mod tests { ThemeSettings::register(cx); release_channel::init(SemanticVersion::default(), cx); EditorSettings::register(cx); + prompt_store::init(cx) }); } @@ -3885,20 +4391,21 @@ pub(crate) mod tests { let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); - let thread_store = - cx.update(|_window, cx| cx.new(|cx| ThreadStore::fake(project.clone(), cx))); - let text_thread_store = - cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx))); + let context_store = + cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx))); + let history_store = + cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx))); let connection = Rc::new(StubAgentConnection::new()); let thread_view = cx.update(|window, cx| { cx.new(|cx| { AcpThreadView::new( Rc::new(StubAgentServer::new(connection.as_ref().clone())), + None, workspace.downgrade(), project.clone(), - thread_store.clone(), - text_thread_store.clone(), + history_store.clone(), + None, window, cx, ) @@ -4098,6 +4605,32 @@ pub(crate) mod tests { }); } + #[gpui::test] + async fn test_message_doesnt_send_if_empty(cx: &mut TestAppContext) { + init_test(cx); + + let connection = StubAgentConnection::new(); + + let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await; + add_to_workspace(thread_view.clone(), cx); + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + let mut events = cx.events(&message_editor); + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("", window, cx); + }); + + message_editor.update_in(cx, |_editor, window, cx| { + window.dispatch_action(Box::new(Chat), cx); + }); + cx.run_until_parked(); + // We shouldn't have received any messages + assert!(matches!( + events.try_next(), + Err(futures::channel::mpsc::TryRecvError { .. }) + )); + } + #[gpui::test] async fn test_message_editing_regenerate(cx: &mut TestAppContext) { init_test(cx); @@ -4182,4 +4715,221 @@ pub(crate) mod tests { assert_eq!(new_editor.read(cx).text(cx), "Edited message content"); }) } + + #[gpui::test] + async fn test_message_editing_while_generating(cx: &mut TestAppContext) { + init_test(cx); + + let connection = StubAgentConnection::new(); + + let (thread_view, cx) = + setup_thread_view(StubAgentServer::new(connection.clone()), cx).await; + add_to_workspace(thread_view.clone(), cx); + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Original message to edit", window, cx); + }); + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + cx.run_until_parked(); + + let (user_message_editor, session_id) = thread_view.read_with(cx, |view, cx| { + let thread = view.thread().unwrap().read(cx); + assert_eq!(thread.entries().len(), 1); + + let editor = view + .entry_view_state + .read(cx) + .entry(0) + .unwrap() + .message_editor() + .unwrap() + .clone(); + + (editor, thread.session_id().clone()) + }); + + // Focus + cx.focus(&user_message_editor); + + thread_view.read_with(cx, |view, _cx| { + assert_eq!(view.editing_message, Some(0)); + }); + + // Edit + user_message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Edited message content", window, cx); + }); + + thread_view.read_with(cx, |view, _cx| { + assert_eq!(view.editing_message, Some(0)); + }); + + // Finish streaming response + cx.update(|_, cx| { + connection.send_update( + session_id.clone(), + acp::SessionUpdate::AgentMessageChunk { + content: acp::ContentBlock::Text(acp::TextContent { + text: "Response".into(), + annotations: None, + }), + }, + cx, + ); + connection.end_turn(session_id, acp::StopReason::EndTurn); + }); + + thread_view.read_with(cx, |view, _cx| { + assert_eq!(view.editing_message, Some(0)); + }); + + cx.run_until_parked(); + + // Should still be editing + cx.update(|window, cx| { + assert!(user_message_editor.focus_handle(cx).is_focused(window)); + assert_eq!(thread_view.read(cx).editing_message, Some(0)); + assert_eq!( + user_message_editor.read(cx).text(cx), + "Edited message content" + ); + }); + } + + #[gpui::test] + async fn test_interrupt(cx: &mut TestAppContext) { + init_test(cx); + + let connection = StubAgentConnection::new(); + + let (thread_view, cx) = + setup_thread_view(StubAgentServer::new(connection.clone()), cx).await; + add_to_workspace(thread_view.clone(), cx); + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Message 1", window, cx); + }); + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + let (thread, session_id) = thread_view.read_with(cx, |view, cx| { + let thread = view.thread().unwrap(); + + (thread.clone(), thread.read(cx).session_id().clone()) + }); + + cx.run_until_parked(); + + cx.update(|_, cx| { + connection.send_update( + session_id.clone(), + acp::SessionUpdate::AgentMessageChunk { + content: "Message 1 resp".into(), + }, + cx, + ); + }); + + cx.run_until_parked(); + + thread.read_with(cx, |thread, cx| { + assert_eq!( + thread.to_markdown(cx), + indoc::indoc! {" + ## User + + Message 1 + + ## Assistant + + Message 1 resp + + "} + ) + }); + + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Message 2", window, cx); + }); + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + cx.update(|_, cx| { + // Simulate a response sent after beginning to cancel + connection.send_update( + session_id.clone(), + acp::SessionUpdate::AgentMessageChunk { + content: "onse".into(), + }, + cx, + ); + }); + + cx.run_until_parked(); + + // Last Message 1 response should appear before Message 2 + thread.read_with(cx, |thread, cx| { + assert_eq!( + thread.to_markdown(cx), + indoc::indoc! {" + ## User + + Message 1 + + ## Assistant + + Message 1 response + + ## User + + Message 2 + + "} + ) + }); + + cx.update(|_, cx| { + connection.send_update( + session_id.clone(), + acp::SessionUpdate::AgentMessageChunk { + content: "Message 2 response".into(), + }, + cx, + ); + connection.end_turn(session_id.clone(), acp::StopReason::EndTurn); + }); + + cx.run_until_parked(); + + thread.read_with(cx, |thread, cx| { + assert_eq!( + thread.to_markdown(cx), + indoc::indoc! {" + ## User + + Message 1 + + ## Assistant + + Message 1 response + + ## User + + Message 2 + + ## Assistant + + Message 2 response + + "} + ) + }); + } } diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs index a0dbb0fe58..cfa0db01d5 100644 --- a/crates/agent_ui/src/active_thread.rs +++ b/crates/agent_ui/src/active_thread.rs @@ -1040,12 +1040,12 @@ impl ActiveThread { ); } ThreadEvent::StreamedAssistantText(message_id, text) => { - if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { + if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(message_id) { rendered_message.append_text(text, cx); } } ThreadEvent::StreamedAssistantThinking(message_id, text) => { - if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { + if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(message_id) { rendered_message.append_thinking(text, cx); } } @@ -1068,8 +1068,8 @@ impl ActiveThread { } ThreadEvent::MessageEdited(message_id) => { self.clear_last_error(); - if let Some(index) = self.messages.iter().position(|id| id == message_id) { - if let Some(rendered_message) = self.thread.update(cx, |thread, cx| { + if let Some(index) = self.messages.iter().position(|id| id == message_id) + && let Some(rendered_message) = self.thread.update(cx, |thread, cx| { thread.message(*message_id).map(|message| { let mut rendered_message = RenderedMessage { language_registry: self.language_registry.clone(), @@ -1080,14 +1080,14 @@ impl ActiveThread { } rendered_message }) - }) { - self.list_state.splice(index..index + 1, 1); - self.rendered_messages_by_id - .insert(*message_id, rendered_message); - self.scroll_to_bottom(cx); - self.save_thread(cx); - cx.notify(); - } + }) + { + self.list_state.splice(index..index + 1, 1); + self.rendered_messages_by_id + .insert(*message_id, rendered_message); + self.scroll_to_bottom(cx); + self.save_thread(cx); + cx.notify(); } } ThreadEvent::MessageDeleted(message_id) => { @@ -1268,62 +1268,61 @@ impl ActiveThread { }) }) .log_err() + && let Some(pop_up) = screen_window.entity(cx).log_err() { - if let Some(pop_up) = screen_window.entity(cx).log_err() { - self.notification_subscriptions - .entry(screen_window) - .or_insert_with(Vec::new) - .push(cx.subscribe_in(&pop_up, window, { - |this, _, event, window, cx| match event { - AgentNotificationEvent::Accepted => { - let handle = window.window_handle(); - cx.activate(true); + self.notification_subscriptions + .entry(screen_window) + .or_insert_with(Vec::new) + .push(cx.subscribe_in(&pop_up, window, { + |this, _, event, window, cx| match event { + AgentNotificationEvent::Accepted => { + let handle = window.window_handle(); + cx.activate(true); - let workspace_handle = this.workspace.clone(); + let workspace_handle = this.workspace.clone(); - // If there are multiple Zed windows, activate the correct one. - cx.defer(move |cx| { - handle - .update(cx, |_view, window, _cx| { - window.activate_window(); + // If there are multiple Zed windows, activate the correct one. + cx.defer(move |cx| { + handle + .update(cx, |_view, window, _cx| { + window.activate_window(); - if let Some(workspace) = workspace_handle.upgrade() { - workspace.update(_cx, |workspace, cx| { - workspace.focus_panel::(window, cx); - }); - } - }) - .log_err(); - }); + if let Some(workspace) = workspace_handle.upgrade() { + workspace.update(_cx, |workspace, cx| { + workspace.focus_panel::(window, cx); + }); + } + }) + .log_err(); + }); - this.dismiss_notifications(cx); - } - AgentNotificationEvent::Dismissed => { - this.dismiss_notifications(cx); - } + this.dismiss_notifications(cx); } - })); + AgentNotificationEvent::Dismissed => { + this.dismiss_notifications(cx); + } + } + })); - self.notifications.push(screen_window); + self.notifications.push(screen_window); - // If the user manually refocuses the original window, dismiss the popup. - self.notification_subscriptions - .entry(screen_window) - .or_insert_with(Vec::new) - .push({ - let pop_up_weak = pop_up.downgrade(); + // If the user manually refocuses the original window, dismiss the popup. + self.notification_subscriptions + .entry(screen_window) + .or_insert_with(Vec::new) + .push({ + let pop_up_weak = pop_up.downgrade(); - cx.observe_window_activation(window, move |_, window, cx| { - if window.is_window_active() { - if let Some(pop_up) = pop_up_weak.upgrade() { - pop_up.update(cx, |_, cx| { - cx.emit(AgentNotificationEvent::Dismissed); - }); - } - } - }) - }); - } + cx.observe_window_activation(window, move |_, window, cx| { + if window.is_window_active() + && let Some(pop_up) = pop_up_weak.upgrade() + { + pop_up.update(cx, |_, cx| { + cx.emit(AgentNotificationEvent::Dismissed); + }); + } + }) + }); } } @@ -1370,12 +1369,12 @@ impl ActiveThread { editor.focus_handle(cx).focus(window); editor.move_to_end(&editor::actions::MoveToEnd, window, cx); }); - let buffer_edited_subscription = cx.subscribe(&editor, |this, _, event, cx| match event { - EditorEvent::BufferEdited => { - this.update_editing_message_token_count(true, cx); - } - _ => {} - }); + let buffer_edited_subscription = + cx.subscribe(&editor, |this, _, event: &EditorEvent, cx| { + if event == &EditorEvent::BufferEdited { + this.update_editing_message_token_count(true, cx); + } + }); let context_picker_menu_handle = PopoverMenuHandle::default(); let context_strip = cx.new(|cx| { @@ -2243,9 +2242,7 @@ impl ActiveThread { let after_editing_message = self .editing_message .as_ref() - .map_or(false, |(editing_message_id, _)| { - message_id > *editing_message_id - }); + .is_some_and(|(editing_message_id, _)| message_id > *editing_message_id); let backdrop = div() .id(("backdrop", ix)) @@ -2265,13 +2262,12 @@ impl ActiveThread { let mut error = None; if let Some(last_restore_checkpoint) = self.thread.read(cx).last_restore_checkpoint() + && last_restore_checkpoint.message_id() == message_id { - if last_restore_checkpoint.message_id() == message_id { - match last_restore_checkpoint { - LastRestoreCheckpoint::Pending { .. } => is_pending = true, - LastRestoreCheckpoint::Error { error: err, .. } => { - error = Some(err.clone()); - } + match last_restore_checkpoint { + LastRestoreCheckpoint::Pending { .. } => is_pending = true, + LastRestoreCheckpoint::Error { error: err, .. } => { + error = Some(err.clone()); } } } @@ -2469,7 +2465,7 @@ impl ActiveThread { message_id, index, content.clone(), - &scroll_handle, + scroll_handle, Some(index) == pending_thinking_segment_index, window, cx, @@ -2593,7 +2589,7 @@ impl ActiveThread { .id(("message-container", ix)) .py_1() .px_2p5() - .child(Banner::new().severity(ui::Severity::Warning).child(message)) + .child(Banner::new().severity(Severity::Warning).child(message)) } fn render_message_thinking_segment( diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index d03a435ff0..946a495f33 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -94,7 +94,7 @@ impl AgentConfiguration { let mut expanded_provider_configurations = HashMap::default(); if LanguageModelRegistry::read_global(cx) .provider(&ZED_CLOUD_PROVIDER_ID) - .map_or(false, |cloud_provider| cloud_provider.must_accept_terms(cx)) + .is_some_and(|cloud_provider| cloud_provider.must_accept_terms(cx)) { expanded_provider_configurations.insert(ZED_CLOUD_PROVIDER_ID, true); } @@ -134,7 +134,11 @@ impl AgentConfiguration { window: &mut Window, cx: &mut Context, ) { - let configuration_view = provider.configuration_view(window, cx); + let configuration_view = provider.configuration_view( + language_model::ConfigurationViewTargetAgent::ZedAgent, + window, + cx, + ); self.configuration_views_by_provider .insert(provider.id(), configuration_view); } @@ -951,7 +955,7 @@ impl AgentConfiguration { } parent.child(v_flex().py_1p5().px_1().gap_1().children( - tools.into_iter().enumerate().map(|(ix, tool)| { + tools.iter().enumerate().map(|(ix, tool)| { h_flex() .id(("tool-item", ix)) .px_1() diff --git a/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs b/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs index c68c9c2730..998641bf01 100644 --- a/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs +++ b/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs @@ -454,7 +454,7 @@ impl Render for AddLlmProviderModal { this.section( Section::new().child( Banner::new() - .severity(ui::Severity::Warning) + .severity(Severity::Warning) .child(div().text_xs().child(error)), ), ) diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs index 32360dd56e..6159b9be80 100644 --- a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs @@ -163,10 +163,10 @@ impl ConfigurationSource { .read(cx) .text(cx); let settings = serde_json_lenient::from_str::(&text)?; - if let Some(settings_validator) = settings_validator { - if let Err(error) = settings_validator.validate(&settings) { - return Err(anyhow::anyhow!(error.to_string())); - } + if let Some(settings_validator) = settings_validator + && let Err(error) = settings_validator.validate(&settings) + { + return Err(anyhow::anyhow!(error.to_string())); } Ok(( id.clone(), @@ -487,7 +487,7 @@ impl ConfigureContextServerModal { } fn render_modal_description(&self, window: &mut Window, cx: &mut Context) -> AnyElement { - const MODAL_DESCRIPTION: &'static str = "Visit the MCP server configuration docs to find all necessary arguments and environment variables."; + const MODAL_DESCRIPTION: &str = "Visit the MCP server configuration docs to find all necessary arguments and environment variables."; if let ConfigurationSource::Extension { installation_instructions: Some(installation_instructions), @@ -716,24 +716,24 @@ fn wait_for_context_server( project::context_server_store::Event::ServerStatusChanged { server_id, status } => { match status { ContextServerStatus::Running => { - if server_id == &context_server_id { - if let Some(tx) = tx.lock().unwrap().take() { - let _ = tx.send(Ok(())); - } + if server_id == &context_server_id + && let Some(tx) = tx.lock().unwrap().take() + { + let _ = tx.send(Ok(())); } } ContextServerStatus::Stopped => { - if server_id == &context_server_id { - if let Some(tx) = tx.lock().unwrap().take() { - let _ = tx.send(Err("Context server stopped running".into())); - } + if server_id == &context_server_id + && let Some(tx) = tx.lock().unwrap().take() + { + let _ = tx.send(Err("Context server stopped running".into())); } } ContextServerStatus::Error(error) => { - if server_id == &context_server_id { - if let Some(tx) = tx.lock().unwrap().take() { - let _ = tx.send(Err(error.clone())); - } + if server_id == &context_server_id + && let Some(tx) = tx.lock().unwrap().take() + { + let _ = tx.send(Err(error.clone())); } } _ => {} diff --git a/crates/agent_ui/src/agent_configuration/tool_picker.rs b/crates/agent_ui/src/agent_configuration/tool_picker.rs index 8f1e0d71c0..25947a1e58 100644 --- a/crates/agent_ui/src/agent_configuration/tool_picker.rs +++ b/crates/agent_ui/src/agent_configuration/tool_picker.rs @@ -191,10 +191,10 @@ impl PickerDelegate for ToolPickerDelegate { BTreeMap::default(); for item in all_items.iter() { - if let PickerItem::Tool { server_id, name } = item.clone() { - if name.contains(&query) { - tools_by_provider.entry(server_id).or_default().push(name); - } + if let PickerItem::Tool { server_id, name } = item.clone() + && name.contains(&query) + { + tools_by_provider.entry(server_id).or_default().push(name); } } diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index b9e1ea5d0a..b20b126d9b 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -199,24 +199,21 @@ impl AgentDiffPane { let action_log = thread.action_log(cx).clone(); let mut this = Self { - _subscriptions: [ - Some( - cx.observe_in(&action_log, window, |this, _action_log, window, cx| { - this.update_excerpts(window, cx) - }), - ), + _subscriptions: vec![ + cx.observe_in(&action_log, window, |this, _action_log, window, cx| { + this.update_excerpts(window, cx) + }), match &thread { - AgentDiffThread::Native(thread) => { - Some(cx.subscribe(&thread, |this, _thread, event, cx| { - this.handle_thread_event(event, cx) - })) - } - AgentDiffThread::AcpThread(_) => None, + AgentDiffThread::Native(thread) => cx + .subscribe(thread, |this, _thread, event, cx| { + this.handle_native_thread_event(event, cx) + }), + AgentDiffThread::AcpThread(thread) => cx + .subscribe(thread, |this, _thread, event, cx| { + this.handle_acp_thread_event(event, cx) + }), }, - ] - .into_iter() - .flatten() - .collect(), + ], title: SharedString::default(), multibuffer, editor, @@ -288,7 +285,7 @@ impl AgentDiffPane { && buffer .read(cx) .file() - .map_or(false, |file| file.disk_state() == DiskState::Deleted) + .is_some_and(|file| file.disk_state() == DiskState::Deleted) { editor.fold_buffer(snapshot.text.remote_id(), cx) } @@ -324,10 +321,15 @@ impl AgentDiffPane { } } - fn handle_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context) { - match event { - ThreadEvent::SummaryGenerated => self.update_title(cx), - _ => {} + fn handle_native_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context) { + if let ThreadEvent::SummaryGenerated = event { + self.update_title(cx) + } + } + + fn handle_acp_thread_event(&mut self, event: &AcpThreadEvent, cx: &mut Context) { + if let AcpThreadEvent::TitleUpdated = event { + self.update_title(cx) } } @@ -398,7 +400,7 @@ fn keep_edits_in_selection( .disjoint_anchor_ranges() .collect::>(); - keep_edits_in_ranges(editor, buffer_snapshot, &thread, ranges, window, cx) + keep_edits_in_ranges(editor, buffer_snapshot, thread, ranges, window, cx) } fn reject_edits_in_selection( @@ -412,7 +414,7 @@ fn reject_edits_in_selection( .selections .disjoint_anchor_ranges() .collect::>(); - reject_edits_in_ranges(editor, buffer_snapshot, &thread, ranges, window, cx) + reject_edits_in_ranges(editor, buffer_snapshot, thread, ranges, window, cx) } fn keep_edits_in_ranges( @@ -503,8 +505,7 @@ fn update_editor_selection( &[last_kept_hunk_end..editor::Anchor::max()], buffer_snapshot, ) - .skip(1) - .next() + .nth(1) }) .or_else(|| { let first_kept_hunk = diff_hunks.first()?; @@ -1001,7 +1002,7 @@ impl AgentDiffToolbar { return; }; - *state = agent_diff.read(cx).editor_state(&editor); + *state = agent_diff.read(cx).editor_state(editor); self.update_location(cx); cx.notify(); } @@ -1044,23 +1045,23 @@ impl ToolbarItemView for AgentDiffToolbar { return self.location(cx); } - if let Some(editor) = item.act_as::(cx) { - if editor.read(cx).mode().is_full() { - let agent_diff = AgentDiff::global(cx); + if let Some(editor) = item.act_as::(cx) + && editor.read(cx).mode().is_full() + { + let agent_diff = AgentDiff::global(cx); - self.active_item = Some(AgentDiffToolbarItem::Editor { - editor: editor.downgrade(), - state: agent_diff.read(cx).editor_state(&editor.downgrade()), - _diff_subscription: cx.observe(&agent_diff, Self::handle_diff_notify), - }); + self.active_item = Some(AgentDiffToolbarItem::Editor { + editor: editor.downgrade(), + state: agent_diff.read(cx).editor_state(&editor.downgrade()), + _diff_subscription: cx.observe(&agent_diff, Self::handle_diff_notify), + }); - return self.location(cx); - } + return self.location(cx); } } self.active_item = None; - return self.location(cx); + self.location(cx) } fn pane_focus_update( @@ -1343,13 +1344,13 @@ impl AgentDiff { }); let thread_subscription = match &thread { - AgentDiffThread::Native(thread) => cx.subscribe_in(&thread, window, { + AgentDiffThread::Native(thread) => cx.subscribe_in(thread, window, { let workspace = workspace.clone(); move |this, _thread, event, window, cx| { this.handle_native_thread_event(&workspace, event, window, cx) } }), - AgentDiffThread::AcpThread(thread) => cx.subscribe_in(&thread, window, { + AgentDiffThread::AcpThread(thread) => cx.subscribe_in(thread, window, { let workspace = workspace.clone(); move |this, thread, event, window, cx| { this.handle_acp_thread_event(&workspace, thread, event, window, cx) @@ -1357,11 +1358,11 @@ impl AgentDiff { }), }; - if let Some(workspace_thread) = self.workspace_threads.get_mut(&workspace) { + if let Some(workspace_thread) = self.workspace_threads.get_mut(workspace) { // replace thread and action log subscription, but keep editors workspace_thread.thread = thread.downgrade(); workspace_thread._thread_subscriptions = (action_log_subscription, thread_subscription); - self.update_reviewing_editors(&workspace, window, cx); + self.update_reviewing_editors(workspace, window, cx); return; } @@ -1506,7 +1507,7 @@ impl AgentDiff { .read(cx) .entries() .last() - .map_or(false, |entry| entry.diffs().next().is_some()) + .is_some_and(|entry| entry.diffs().next().is_some()) { self.update_reviewing_editors(workspace, window, cx); } @@ -1516,16 +1517,19 @@ impl AgentDiff { .read(cx) .entries() .get(*ix) - .map_or(false, |entry| entry.diffs().next().is_some()) + .is_some_and(|entry| entry.diffs().next().is_some()) { self.update_reviewing_editors(workspace, window, cx); } } - AcpThreadEvent::EntriesRemoved(_) - | AcpThreadEvent::Stopped + AcpThreadEvent::Stopped | AcpThreadEvent::Error | AcpThreadEvent::LoadError(_) => { + self.update_reviewing_editors(workspace, window, cx); + } + AcpThreadEvent::TitleUpdated + | AcpThreadEvent::TokenUsageUpdated + | AcpThreadEvent::EntriesRemoved(_) | AcpThreadEvent::ToolAuthorizationRequired - | AcpThreadEvent::Error - | AcpThreadEvent::ServerExited(_) => {} + | AcpThreadEvent::Retry(_) => {} } } @@ -1536,21 +1540,11 @@ impl AgentDiff { window: &mut Window, cx: &mut Context, ) { - match event { - workspace::Event::ItemAdded { item } => { - if let Some(editor) = item.downcast::() { - if let Some(buffer) = Self::full_editor_buffer(editor.read(cx), cx) { - self.register_editor( - workspace.downgrade(), - buffer.clone(), - editor, - window, - cx, - ); - } - } - } - _ => {} + if let workspace::Event::ItemAdded { item } = event + && let Some(editor) = item.downcast::() + && let Some(buffer) = Self::full_editor_buffer(editor.read(cx), cx) + { + self.register_editor(workspace.downgrade(), buffer.clone(), editor, window, cx); } } @@ -1677,7 +1671,7 @@ impl AgentDiff { editor.register_addon(EditorAgentDiffAddon); }); } else { - unaffected.remove(&weak_editor); + unaffected.remove(weak_editor); } if new_state == EditorState::Reviewing && previous_state != Some(new_state) { @@ -1710,7 +1704,7 @@ impl AgentDiff { .read_with(cx, |editor, _cx| editor.workspace()) .ok() .flatten() - .map_or(false, |editor_workspace| { + .is_some_and(|editor_workspace| { editor_workspace.entity_id() == workspace.entity_id() }); @@ -1730,7 +1724,7 @@ impl AgentDiff { fn editor_state(&self, editor: &WeakEntity) -> EditorState { self.reviewing_editors - .get(&editor) + .get(editor) .cloned() .unwrap_or(EditorState::Idle) } @@ -1850,26 +1844,26 @@ impl AgentDiff { let thread = thread.upgrade()?; - if let PostReviewState::AllReviewed = review(&editor, &thread, window, cx) { - if let Some(curr_buffer) = editor.read(cx).buffer().read(cx).as_singleton() { - let changed_buffers = thread.action_log(cx).read(cx).changed_buffers(cx); + if let PostReviewState::AllReviewed = review(&editor, &thread, window, cx) + && let Some(curr_buffer) = editor.read(cx).buffer().read(cx).as_singleton() + { + let changed_buffers = thread.action_log(cx).read(cx).changed_buffers(cx); - let mut keys = changed_buffers.keys().cycle(); - keys.find(|k| *k == &curr_buffer); - let next_project_path = keys - .next() - .filter(|k| *k != &curr_buffer) - .and_then(|after| after.read(cx).project_path(cx)); + let mut keys = changed_buffers.keys().cycle(); + keys.find(|k| *k == &curr_buffer); + let next_project_path = keys + .next() + .filter(|k| *k != &curr_buffer) + .and_then(|after| after.read(cx).project_path(cx)); - if let Some(path) = next_project_path { - let task = workspace.open_path(path, None, true, window, cx); - let task = cx.spawn(async move |_, _cx| task.await.map(|_| ())); - return Some(task); - } + if let Some(path) = next_project_path { + let task = workspace.open_path(path, None, true, window, cx); + let task = cx.spawn(async move |_, _cx| task.await.map(|_| ())); + return Some(task); } } - return Some(Task::ready(Ok(()))); + Some(Task::ready(Ok(()))) } } diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 4cb231f357..b857052d69 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -4,11 +4,12 @@ use std::rc::Rc; use std::sync::Arc; use std::time::Duration; -use agent_servers::AgentServer; +use acp_thread::AcpThread; +use agent2::{DbThreadMetadata, HistoryEntry}; use db::kvp::{Dismissable, KEY_VALUE_STORE}; use serde::{Deserialize, Serialize}; -use crate::NewExternalAgentThread; +use crate::acp::{AcpThreadHistory, ThreadHistoryEvent}; use crate::agent_diff::AgentDiffThread; use crate::{ AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode, @@ -29,6 +30,7 @@ use crate::{ thread_history::{HistoryEntryElement, ThreadHistory}, ui::{AgentOnboardingModal, EndTrialUpsell}, }; +use crate::{ExternalAgent, NewExternalAgentThread}; use agent::{ Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio, context_store::ContextStore, @@ -44,13 +46,12 @@ use assistant_tool::ToolWorkingSet; use client::{UserStore, zed_urls}; use cloud_llm_client::{CompletionIntent, Plan, UsageLimit}; use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer}; -use feature_flags::{self, FeatureFlagAppExt}; +use feature_flags::{self, ClaudeCodeFeatureFlag, FeatureFlagAppExt, GeminiAndNativeFeatureFlag}; use fs::Fs; use gpui::{ Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem, - Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, Hsla, - KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, - pulsating_between, + Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, + Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between, }; use language::LanguageRegistry; use language_model::{ @@ -65,8 +66,8 @@ use theme::ThemeSettings; use time::UtcOffset; use ui::utils::WithRemSize; use ui::{ - Banner, Callout, ContextMenu, ContextMenuEntry, Divider, ElevationIndex, KeyBinding, - PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*, + Banner, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding, PopoverMenu, + PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*, }; use util::ResultExt as _; use workspace::{ @@ -119,7 +120,7 @@ pub fn init(cx: &mut App) { if let Some(panel) = workspace.panel::(cx) { workspace.focus_panel::(window, cx); panel.update(cx, |panel, cx| { - panel.new_external_thread(action.agent, window, cx) + panel.external_thread(action.agent, None, window, cx) }); } }) @@ -245,17 +246,16 @@ impl AgentType { match self { Self::Zed | Self::TextThread => "Zed Agent", Self::NativeAgent => "Agent 2", - Self::Gemini => "Google Gemini", + Self::Gemini => "Gemini CLI", Self::ClaudeCode => "Claude Code", } } - fn icon(self) -> IconName { + fn icon(self) -> Option { match self { - Self::Zed | Self::TextThread => IconName::AiZed, - Self::NativeAgent => IconName::ZedAssistant, - Self::Gemini => IconName::AiGemini, - Self::ClaudeCode => IconName::AiClaude, + Self::Zed | Self::NativeAgent | Self::TextThread => None, + Self::Gemini => Some(IconName::AiGemini), + Self::ClaudeCode => Some(IconName::AiClaude), } } } @@ -354,7 +354,7 @@ impl ActiveView { Self::Thread { change_title_editor: editor, thread: active_thread, - message_editor: message_editor, + message_editor, _subscriptions: subscriptions, } } @@ -362,6 +362,7 @@ impl ActiveView { pub fn prompt_editor( context_editor: Entity, history_store: Entity, + acp_history_store: Entity, language_registry: Arc, window: &mut Window, cx: &mut App, @@ -439,6 +440,18 @@ impl ActiveView { ); } }); + + acp_history_store.update(cx, |history_store, cx| { + if let Some(old_path) = old_path { + history_store + .replace_recently_opened_text_thread(old_path, new_path, cx); + } else { + history_store.push_recently_opened_entry( + agent2::HistoryEntryId::TextThread(new_path.clone()), + cx, + ); + } + }); } _ => {} } @@ -467,6 +480,8 @@ pub struct AgentPanel { fs: Arc, language_registry: Arc, thread_store: Entity, + acp_history: Entity, + acp_history_store: Entity, _default_model_subscription: Subscription, context_store: Entity, prompt_store: Option>, @@ -633,6 +648,28 @@ impl AgentPanel { ) }); + let acp_history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx)); + let acp_history = cx.new(|cx| AcpThreadHistory::new(acp_history_store.clone(), window, cx)); + cx.subscribe_in( + &acp_history, + window, + |this, _, event, window, cx| match event { + ThreadHistoryEvent::Open(HistoryEntry::AcpThread(thread)) => { + this.external_thread( + Some(crate::ExternalAgent::NativeAgent), + Some(thread.clone()), + window, + cx, + ); + } + ThreadHistoryEvent::Open(HistoryEntry::TextThread(thread)) => { + this.open_saved_prompt_editor(thread.path.clone(), window, cx) + .detach_and_log_err(cx); + } + }, + ) + .detach(); + cx.observe(&history_store, |_, _, cx| cx.notify()).detach(); let active_thread = cx.new(|cx| { @@ -671,6 +708,7 @@ impl AgentPanel { ActiveView::prompt_editor( context_editor, history_store.clone(), + acp_history_store.clone(), language_registry.clone(), window, cx, @@ -687,7 +725,11 @@ impl AgentPanel { let assistant_navigation_menu = ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| { if let Some(panel) = panel.upgrade() { - menu = Self::populate_recently_opened_menu_section(menu, panel, cx); + if cx.has_flag::() { + menu = Self::populate_recently_opened_menu_section_new(menu, panel, cx); + } else { + menu = Self::populate_recently_opened_menu_section_old(menu, panel, cx); + } } menu.action("View All", Box::new(OpenHistory)) .end_slot_action(DeleteRecentlyOpenThread.boxed_clone()) @@ -713,25 +755,25 @@ impl AgentPanel { .ok(); }); - let _default_model_subscription = cx.subscribe( - &LanguageModelRegistry::global(cx), - |this, _, event: &language_model::Event, cx| match event { - language_model::Event::DefaultModelChanged => match &this.active_view { - ActiveView::Thread { thread, .. } => { - thread - .read(cx) - .thread() - .clone() - .update(cx, |thread, cx| thread.get_or_init_configured_model(cx)); + let _default_model_subscription = + cx.subscribe( + &LanguageModelRegistry::global(cx), + |this, _, event: &language_model::Event, cx| { + if let language_model::Event::DefaultModelChanged = event { + match &this.active_view { + ActiveView::Thread { thread, .. } => { + thread.read(cx).thread().clone().update(cx, |thread, cx| { + thread.get_or_init_configured_model(cx) + }); + } + ActiveView::ExternalAgentThread { .. } + | ActiveView::TextThread { .. } + | ActiveView::History + | ActiveView::Configuration => {} + } } - ActiveView::ExternalAgentThread { .. } - | ActiveView::TextThread { .. } - | ActiveView::History - | ActiveView::Configuration => {} }, - _ => {} - }, - ); + ); let onboarding = cx.new(|cx| { AgentPanelOnboarding::new( @@ -775,6 +817,8 @@ impl AgentPanel { zoomed: false, pending_serialization: None, onboarding, + acp_history, + acp_history_store, selected_agent: AgentType::default(), } } @@ -837,6 +881,9 @@ impl AgentPanel { } fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context) { + if cx.has_flag::() { + return self.new_agent_thread(AgentType::NativeAgent, window, cx); + } // Preserve chat box text when using creating new thread let preserved_text = self .active_message_editor() @@ -941,6 +988,7 @@ impl AgentPanel { ActiveView::prompt_editor( context_editor.clone(), self.history_store.clone(), + self.acp_history_store.clone(), self.language_registry.clone(), window, cx, @@ -951,9 +999,10 @@ impl AgentPanel { context_editor.focus_handle(cx).focus(window); } - fn new_external_thread( + fn external_thread( &mut self, agent_choice: Option, + resume_thread: Option, window: &mut Window, cx: &mut Context, ) { @@ -968,11 +1017,10 @@ impl AgentPanel { agent: crate::ExternalAgent, } - let thread_store = self.thread_store.clone(); - let text_thread_store = self.context_store.clone(); + let history = self.acp_history_store.clone(); cx.spawn_in(window, async move |this, cx| { - let server: Rc = match agent_choice { + let ext_agent = match agent_choice { Some(agent) => { cx.background_spawn(async move { if let Some(serialized) = @@ -986,10 +1034,10 @@ impl AgentPanel { }) .detach(); - agent.server(fs) + agent } - None => cx - .background_spawn(async move { + None => { + cx.background_spawn(async move { KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY) }) .await @@ -1000,17 +1048,33 @@ impl AgentPanel { }) .unwrap_or_default() .agent - .server(fs), + } }; + let server = ext_agent.server(fs, history); + this.update_in(cx, |this, window, cx| { + match ext_agent { + crate::ExternalAgent::Gemini | crate::ExternalAgent::NativeAgent => { + if !cx.has_flag::() { + return; + } + } + crate::ExternalAgent::ClaudeCode => { + if !cx.has_flag::() { + return; + } + } + } + let thread_view = cx.new(|cx| { crate::acp::AcpThreadView::new( server, + resume_thread, workspace.clone(), project, - thread_store.clone(), - text_thread_store.clone(), + this.acp_history_store.clone(), + this.prompt_store.clone(), window, cx, ) @@ -1101,6 +1165,7 @@ impl AgentPanel { ActiveView::prompt_editor( editor.clone(), self.history_store.clone(), + self.acp_history_store.clone(), self.language_registry.clone(), window, cx, @@ -1398,15 +1463,14 @@ impl AgentPanel { AssistantConfigurationEvent::NewThread(provider) => { if LanguageModelRegistry::read_global(cx) .default_model() - .map_or(true, |model| model.provider.id() != provider.id()) + .is_none_or(|model| model.provider.id() != provider.id()) + && let Some(model) = provider.default_model(cx) { - if let Some(model) = provider.default_model(cx) { - update_settings_file::( - self.fs.clone(), - cx, - move |settings, _| settings.set_model(model), - ); - } + update_settings_file::( + self.fs.clone(), + cx, + move |settings, _| settings.set_model(model), + ); } self.new_thread(&NewThread::default(), window, cx); @@ -1433,6 +1497,14 @@ impl AgentPanel { _ => None, } } + pub(crate) fn active_agent_thread(&self, cx: &App) -> Option> { + match &self.active_view { + ActiveView::ExternalAgentThread { thread_view, .. } => { + thread_view.read(cx).thread().cloned() + } + _ => None, + } + } pub(crate) fn delete_thread( &mut self, @@ -1525,17 +1597,14 @@ impl AgentPanel { let current_is_special = current_is_history || current_is_config; let new_is_special = new_is_history || new_is_config; - match &self.active_view { - ActiveView::Thread { thread, .. } => { - let thread = thread.read(cx); - if thread.is_empty() { - let id = thread.thread().read(cx).id().clone(); - self.history_store.update(cx, |store, cx| { - store.remove_recently_opened_thread(id, cx); - }); - } + if let ActiveView::Thread { thread, .. } = &self.active_view { + let thread = thread.read(cx); + if thread.is_empty() { + let id = thread.thread().read(cx).id().clone(); + self.history_store.update(cx, |store, cx| { + store.remove_recently_opened_thread(id, cx); + }); } - _ => {} } match &new_view { @@ -1548,6 +1617,14 @@ impl AgentPanel { if let Some(path) = context_editor.read(cx).context().read(cx).path() { store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx) } + }); + self.acp_history_store.update(cx, |store, cx| { + if let Some(path) = context_editor.read(cx).context().read(cx).path() { + store.push_recently_opened_entry( + agent2::HistoryEntryId::TextThread(path.clone()), + cx, + ) + } }) } ActiveView::ExternalAgentThread { .. } => {} @@ -1568,7 +1645,7 @@ impl AgentPanel { self.focus_handle(cx).focus(window); } - fn populate_recently_opened_menu_section( + fn populate_recently_opened_menu_section_old( mut menu: ContextMenu, panel: Entity, cx: &mut Context, @@ -1632,6 +1709,72 @@ impl AgentPanel { menu } + fn populate_recently_opened_menu_section_new( + mut menu: ContextMenu, + panel: Entity, + cx: &mut Context, + ) -> ContextMenu { + let entries = panel + .read(cx) + .acp_history_store + .read(cx) + .recently_opened_entries(cx); + + if entries.is_empty() { + return menu; + } + + menu = menu.header("Recently Opened"); + + for entry in entries { + let title = entry.title().clone(); + + menu = menu.entry_with_end_slot_on_hover( + title, + None, + { + let panel = panel.downgrade(); + let entry = entry.clone(); + move |window, cx| { + let entry = entry.clone(); + panel + .update(cx, move |this, cx| match &entry { + agent2::HistoryEntry::AcpThread(entry) => this.external_thread( + Some(ExternalAgent::NativeAgent), + Some(entry.clone()), + window, + cx, + ), + agent2::HistoryEntry::TextThread(entry) => this + .open_saved_prompt_editor(entry.path.clone(), window, cx) + .detach_and_log_err(cx), + }) + .ok(); + } + }, + IconName::Close, + "Close Entry".into(), + { + let panel = panel.downgrade(); + let id = entry.id(); + move |_window, cx| { + panel + .update(cx, |this, cx| { + this.acp_history_store.update(cx, |history_store, cx| { + history_store.remove_recently_opened_entry(&id, cx); + }); + }) + .ok(); + } + }, + ); + } + + menu = menu.separator(); + + menu + } + pub fn set_selected_agent( &mut self, agent: AgentType, @@ -1641,8 +1784,8 @@ impl AgentPanel { if self.selected_agent != agent { self.selected_agent = agent; self.serialize(cx); - self.new_agent_thread(agent, window, cx); } + self.new_agent_thread(agent, window, cx); } pub fn selected_agent(&self) -> AgentType { @@ -1669,16 +1812,25 @@ impl AgentPanel { window.dispatch_action(NewTextThread.boxed_clone(), cx); } AgentType::NativeAgent => { - self.new_external_thread(Some(crate::ExternalAgent::NativeAgent), window, cx) + self.external_thread(Some(crate::ExternalAgent::NativeAgent), None, window, cx) } AgentType::Gemini => { - self.new_external_thread(Some(crate::ExternalAgent::Gemini), window, cx) + self.external_thread(Some(crate::ExternalAgent::Gemini), None, window, cx) } AgentType::ClaudeCode => { - self.new_external_thread(Some(crate::ExternalAgent::ClaudeCode), window, cx) + self.external_thread(Some(crate::ExternalAgent::ClaudeCode), None, window, cx) } } } + + pub fn load_agent_thread( + &mut self, + thread: DbThreadMetadata, + window: &mut Window, + cx: &mut Context, + ) { + self.external_thread(Some(ExternalAgent::NativeAgent), Some(thread), window, cx); + } } impl Focusable for AgentPanel { @@ -1686,7 +1838,13 @@ impl Focusable for AgentPanel { match &self.active_view { ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx), ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx), - ActiveView::History => self.history.focus_handle(cx), + ActiveView::History => { + if cx.has_flag::() { + self.acp_history.focus_handle(cx) + } else { + self.history.focus_handle(cx) + } + } ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx), ActiveView::Configuration => { if let Some(configuration) = self.configuration.as_ref() { @@ -2014,12 +2172,17 @@ impl AgentPanel { }) } - fn render_recent_entries_menu(&self, cx: &mut Context) -> impl IntoElement { + fn render_recent_entries_menu( + &self, + icon: IconName, + corner: Corner, + cx: &mut Context, + ) -> impl IntoElement { let focus_handle = self.focus_handle(cx); PopoverMenu::new("agent-nav-menu") .trigger_with_tooltip( - IconButton::new("agent-nav-menu", IconName::MenuAlt).icon_size(IconSize::Small), + IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small), { let focus_handle = focus_handle.clone(); move |window, cx| { @@ -2033,7 +2196,7 @@ impl AgentPanel { } }, ) - .anchor(Corner::TopLeft) + .anchor(corner) .with_handle(self.assistant_navigation_menu_handle.clone()) .menu({ let menu = self.assistant_navigation_menu.clone(); @@ -2160,7 +2323,9 @@ impl AgentPanel { .pl(DynamicSpacing::Base04.rems(cx)) .child(self.render_toolbar_back_button(cx)) .into_any_element(), - _ => self.render_recent_entries_menu(cx).into_any_element(), + _ => self + .render_recent_entries_menu(IconName::MenuAlt, Corner::TopLeft, cx) + .into_any_element(), }) .child(self.render_title_view(window, cx)), ) @@ -2245,9 +2410,9 @@ impl AgentPanel { }) .item( ContextMenuEntry::new("New Thread") + .action(NewThread::default().boxed_clone()) .icon(IconName::Thread) .icon_color(Color::Muted) - .action(NewThread::default().boxed_clone()) .handler({ let workspace = workspace.clone(); move |window, cx| { @@ -2258,7 +2423,7 @@ impl AgentPanel { { panel.update(cx, |panel, cx| { panel.set_selected_agent( - AgentType::Zed, + AgentType::NativeAgent, window, cx, ); @@ -2295,83 +2460,62 @@ impl AgentPanel { } }), ) - .item( - ContextMenuEntry::new("New Native Agent Thread") - .icon(IconName::ZedAssistant) - .icon_color(Color::Muted) - .handler({ - let workspace = workspace.clone(); - move |window, cx| { - if let Some(workspace) = workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - if let Some(panel) = - workspace.panel::(cx) - { - panel.update(cx, |panel, cx| { - panel.set_selected_agent( - AgentType::NativeAgent, - window, - cx, - ); - }); - } - }); - } - } - }), - ) .separator() .header("External Agents") - .item( - ContextMenuEntry::new("New Gemini Thread") - .icon(IconName::AiGemini) - .icon_color(Color::Muted) - .handler({ - let workspace = workspace.clone(); - move |window, cx| { - if let Some(workspace) = workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - if let Some(panel) = - workspace.panel::(cx) - { - panel.update(cx, |panel, cx| { - panel.set_selected_agent( - AgentType::Gemini, - window, - cx, - ); - }); - } - }); + .when(cx.has_flag::(), |menu| { + menu.item( + ContextMenuEntry::new("New Gemini CLI Thread") + .icon(IconName::AiGemini) + .icon_color(Color::Muted) + .handler({ + let workspace = workspace.clone(); + move |window, cx| { + if let Some(workspace) = workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + if let Some(panel) = + workspace.panel::(cx) + { + panel.update(cx, |panel, cx| { + panel.set_selected_agent( + AgentType::Gemini, + window, + cx, + ); + }); + } + }); + } } - } - }), - ) - .item( - ContextMenuEntry::new("New Claude Code Thread") - .icon(IconName::AiClaude) - .icon_color(Color::Muted) - .handler({ - let workspace = workspace.clone(); - move |window, cx| { - if let Some(workspace) = workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - if let Some(panel) = - workspace.panel::(cx) - { - panel.update(cx, |panel, cx| { - panel.set_selected_agent( - AgentType::ClaudeCode, - window, - cx, - ); - }); - } - }); + }), + ) + }) + .when(cx.has_flag::(), |menu| { + menu.item( + ContextMenuEntry::new("New Claude Code Thread") + .icon(IconName::AiClaude) + .icon_color(Color::Muted) + .handler({ + let workspace = workspace.clone(); + move |window, cx| { + if let Some(workspace) = workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + if let Some(panel) = + workspace.panel::(cx) + { + panel.update(cx, |panel, cx| { + panel.set_selected_agent( + AgentType::ClaudeCode, + window, + cx, + ); + }); + } + }); + } } - } - }), - ); + }), + ) + }); menu })) } @@ -2380,16 +2524,18 @@ impl AgentPanel { let selected_agent_label = self.selected_agent.label().into(); let selected_agent = div() .id("selected_agent_icon") - .px(DynamicSpacing::Base02.rems(cx)) - .child(Icon::new(self.selected_agent.icon()).color(Color::Muted)) - .tooltip(move |window, cx| { - Tooltip::with_meta( - selected_agent_label.clone(), - None, - "Selected Agent", - window, - cx, - ) + .when_some(self.selected_agent.icon(), |this, icon| { + this.px(DynamicSpacing::Base02.rems(cx)) + .child(Icon::new(icon).color(Color::Muted)) + .tooltip(move |window, cx| { + Tooltip::with_meta( + selected_agent_label.clone(), + None, + "Selected Agent", + window, + cx, + ) + }) }) .into_any_element(); @@ -2412,36 +2558,30 @@ impl AgentPanel { ActiveView::History | ActiveView::Configuration => { self.render_toolbar_back_button(cx).into_any_element() } - _ => h_flex() - .gap_1() - .child(self.render_recent_entries_menu(cx)) - .child(Divider::vertical()) - .child(selected_agent) - .into_any_element(), + _ => selected_agent.into_any_element(), }) .child(self.render_title_view(window, cx)), ) .child( h_flex() - .h_full() - .gap_2() - .children(self.render_token_count(cx)) - .child( - h_flex() - .h_full() - .gap(DynamicSpacing::Base02.rems(cx)) - .pl(DynamicSpacing::Base04.rems(cx)) - .pr(DynamicSpacing::Base06.rems(cx)) - .border_l_1() - .border_color(cx.theme().colors().border) - .child(new_thread_menu) - .child(self.render_panel_options_menu(window, cx)), - ), + .flex_none() + .gap(DynamicSpacing::Base02.rems(cx)) + .pl(DynamicSpacing::Base04.rems(cx)) + .pr(DynamicSpacing::Base06.rems(cx)) + .child(new_thread_menu) + .child(self.render_recent_entries_menu( + IconName::MenuAltTemp, + Corner::TopRight, + cx, + )) + .child(self.render_panel_options_menu(window, cx)), ) } fn render_toolbar(&self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - if cx.has_flag::() { + if cx.has_flag::() + || cx.has_flag::() + { self.render_toolbar_new(window, cx).into_any_element() } else { self.render_toolbar_old(window, cx).into_any_element() @@ -2566,9 +2706,7 @@ impl AgentPanel { } ActiveView::ExternalAgentThread { .. } | ActiveView::History - | ActiveView::Configuration => { - return None; - } + | ActiveView::Configuration => None, } } @@ -2584,7 +2722,7 @@ impl AgentPanel { .thread() .read(cx) .configured_model() - .map_or(false, |model| { + .is_some_and(|model| { model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID }) { @@ -2595,7 +2733,7 @@ impl AgentPanel { if LanguageModelRegistry::global(cx) .read(cx) .default_model() - .map_or(false, |model| { + .is_some_and(|model| { model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID }) { @@ -2626,9 +2764,12 @@ impl AgentPanel { false } _ => { - let history_is_empty = self - .history_store - .update(cx, |store, cx| store.recent_entries(1, cx).is_empty()); + let history_is_empty = if cx.has_flag::() { + self.acp_history_store.read(cx).is_empty(cx) + } else { + self.history_store + .update(cx, |store, cx| store.recent_entries(1, cx).is_empty()) + }; let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx) .providers() @@ -2712,20 +2853,22 @@ impl AgentPanel { action_slot: Option, cx: &mut Context, ) -> impl IntoElement { - h_flex() - .mt_2() - .pl_1p5() - .pb_1() - .w_full() - .justify_between() - .border_b_1() - .border_color(cx.theme().colors().border_variant) - .child( - Label::new(label.into()) - .size(LabelSize::Small) - .color(Color::Muted), - ) - .children(action_slot) + div().pl_1().pr_1p5().child( + h_flex() + .mt_2() + .pl_1p5() + .pb_1() + .w_full() + .justify_between() + .border_b_1() + .border_color(cx.theme().colors().border_variant) + .child( + Label::new(label.into()) + .size(LabelSize::Small) + .color(Color::Muted), + ) + .children(action_slot), + ) } fn render_thread_empty_state( @@ -2831,22 +2974,12 @@ impl AgentPanel { }), ), ) - }) - .when_some(configuration_error.as_ref(), |this, err| { - this.child(self.render_configuration_error( - err, - &focus_handle, - window, - cx, - )) }), ) }) .when(!recent_history.is_empty(), |parent| { - let focus_handle = focus_handle.clone(); parent .overflow_hidden() - .p_1p5() .justify_end() .gap_1() .child( @@ -2874,10 +3007,11 @@ impl AgentPanel { ), ) .child( - v_flex() - .gap_1() - .children(recent_history.into_iter().enumerate().map( - |(index, entry)| { + v_flex().p_1().pr_1p5().gap_1().children( + recent_history + .into_iter() + .enumerate() + .map(|(index, entry)| { // TODO: Add keyboard navigation. let is_hovered = self.hovered_recent_history_item == Some(index); @@ -2896,49 +3030,92 @@ impl AgentPanel { }, )) .into_any_element() - }, - )), + }), + ), ) - .when_some(configuration_error.as_ref(), |this, err| { - this.child(self.render_configuration_error(err, &focus_handle, window, cx)) - }) + }) + .when_some(configuration_error.as_ref(), |this, err| { + this.child(self.render_configuration_error(false, err, &focus_handle, window, cx)) }) } fn render_configuration_error( &self, + border_bottom: bool, configuration_error: &ConfigurationError, focus_handle: &FocusHandle, window: &mut Window, cx: &mut App, ) -> impl IntoElement { - match configuration_error { - ConfigurationError::ModelNotFound - | ConfigurationError::ProviderNotAuthenticated(_) - | ConfigurationError::NoProvider => Banner::new() - .severity(ui::Severity::Warning) - .child(Label::new(configuration_error.to_string())) - .action_slot( - Button::new("settings", "Configure Provider") + let zed_provider_configured = AgentSettings::get_global(cx) + .default_model + .as_ref() + .is_some_and(|selection| selection.provider.0.as_str() == "zed.dev"); + + let callout = if zed_provider_configured { + Callout::new() + .icon(IconName::Warning) + .severity(Severity::Warning) + .when(border_bottom, |this| { + this.border_position(ui::BorderPosition::Bottom) + }) + .title("Sign in to continue using Zed as your LLM provider.") + .actions_slot( + Button::new("sign_in", "Sign In") + .style(ButtonStyle::Tinted(ui::TintColor::Warning)) + .label_size(LabelSize::Small) + .on_click({ + let workspace = self.workspace.clone(); + move |_, _, cx| { + let Ok(client) = + workspace.update(cx, |workspace, _| workspace.client().clone()) + else { + return; + }; + + cx.spawn(async move |cx| { + client.sign_in_with_optional_connect(true, cx).await + }) + .detach_and_log_err(cx); + } + }), + ) + } else { + Callout::new() + .icon(IconName::Warning) + .severity(Severity::Warning) + .when(border_bottom, |this| { + this.border_position(ui::BorderPosition::Bottom) + }) + .title(configuration_error.to_string()) + .actions_slot( + Button::new("settings", "Configure") .style(ButtonStyle::Tinted(ui::TintColor::Warning)) .label_size(LabelSize::Small) .key_binding( - KeyBinding::for_action_in(&OpenSettings, &focus_handle, window, cx) + KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx) .map(|kb| kb.size(rems_from_px(12.))), ) .on_click(|_event, window, cx| { window.dispatch_action(OpenSettings.boxed_clone(), cx) }), - ), + ) + }; + + match configuration_error { + ConfigurationError::ModelNotFound + | ConfigurationError::ProviderNotAuthenticated(_) + | ConfigurationError::NoProvider => callout.into_any_element(), ConfigurationError::ProviderPendingTermsAcceptance(provider) => { - Banner::new().severity(ui::Severity::Warning).child( - h_flex().w_full().children( + Banner::new() + .severity(Severity::Warning) + .child(h_flex().w_full().children( provider.render_accept_terms( LanguageModelProviderTosView::ThreadEmptyState, cx, ), - ), - ) + )) + .into_any_element() } } } @@ -2970,7 +3147,7 @@ impl AgentPanel { let focus_handle = self.focus_handle(cx); let banner = Banner::new() - .severity(ui::Severity::Info) + .severity(Severity::Info) .child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small)) .action_slot( h_flex() @@ -3081,10 +3258,6 @@ impl AgentPanel { })) } - fn error_callout_bg(&self, cx: &Context) -> Hsla { - cx.theme().status().error.opacity(0.08) - } - fn render_payment_required_error( &self, thread: &Entity, @@ -3093,23 +3266,18 @@ impl AgentPanel { const ERROR_MESSAGE: &str = "You reached your free usage limit. Upgrade to Zed Pro for more prompts."; - let icon = Icon::new(IconName::XCircle) - .size(IconSize::Small) - .color(Color::Error); - - div() - .border_t_1() - .border_color(cx.theme().colors().border) - .child( - Callout::new() - .icon(icon) - .title("Free Usage Exceeded") - .description(ERROR_MESSAGE) - .tertiary_action(self.upgrade_button(thread, cx)) - .secondary_action(self.create_copy_button(ERROR_MESSAGE)) - .primary_action(self.dismiss_error_button(thread, cx)) - .bg_color(self.error_callout_bg(cx)), + Callout::new() + .severity(Severity::Error) + .icon(IconName::XCircle) + .title("Free Usage Exceeded") + .description(ERROR_MESSAGE) + .actions_slot( + h_flex() + .gap_0p5() + .child(self.upgrade_button(thread, cx)) + .child(self.create_copy_button(ERROR_MESSAGE)), ) + .dismiss_action(self.dismiss_error_button(thread, cx)) .into_any_element() } @@ -3124,23 +3292,37 @@ impl AgentPanel { Plan::ZedProTrial | Plan::ZedFree => "Upgrade to Zed Pro for more prompts.", }; - let icon = Icon::new(IconName::XCircle) - .size(IconSize::Small) - .color(Color::Error); - - div() - .border_t_1() - .border_color(cx.theme().colors().border) - .child( - Callout::new() - .icon(icon) - .title("Model Prompt Limit Reached") - .description(error_message) - .tertiary_action(self.upgrade_button(thread, cx)) - .secondary_action(self.create_copy_button(error_message)) - .primary_action(self.dismiss_error_button(thread, cx)) - .bg_color(self.error_callout_bg(cx)), + Callout::new() + .severity(Severity::Error) + .title("Model Prompt Limit Reached") + .description(error_message) + .actions_slot( + h_flex() + .gap_0p5() + .child(self.upgrade_button(thread, cx)) + .child(self.create_copy_button(error_message)), ) + .dismiss_action(self.dismiss_error_button(thread, cx)) + .into_any_element() + } + + fn render_retry_button(&self, thread: &Entity) -> AnyElement { + Button::new("retry", "Retry") + .icon(IconName::RotateCw) + .icon_position(IconPosition::Start) + .icon_size(IconSize::Small) + .label_size(LabelSize::Small) + .on_click({ + let thread = thread.clone(); + move |_, window, cx| { + thread.update(cx, |thread, cx| { + thread.clear_last_error(); + thread.thread().update(cx, |thread, cx| { + thread.retry_last_completion(Some(window.window_handle()), cx); + }); + }); + } + }) .into_any_element() } @@ -3153,40 +3335,18 @@ impl AgentPanel { ) -> AnyElement { let message_with_header = format!("{}\n{}", header, message); - let icon = Icon::new(IconName::XCircle) - .size(IconSize::Small) - .color(Color::Error); - - let retry_button = Button::new("retry", "Retry") - .icon(IconName::RotateCw) - .icon_position(IconPosition::Start) - .icon_size(IconSize::Small) - .label_size(LabelSize::Small) - .on_click({ - let thread = thread.clone(); - move |_, window, cx| { - thread.update(cx, |thread, cx| { - thread.clear_last_error(); - thread.thread().update(cx, |thread, cx| { - thread.retry_last_completion(Some(window.window_handle()), cx); - }); - }); - } - }); - - div() - .border_t_1() - .border_color(cx.theme().colors().border) - .child( - Callout::new() - .icon(icon) - .title(header) - .description(message.clone()) - .primary_action(retry_button) - .secondary_action(self.dismiss_error_button(thread, cx)) - .tertiary_action(self.create_copy_button(message_with_header)) - .bg_color(self.error_callout_bg(cx)), + Callout::new() + .severity(Severity::Error) + .icon(IconName::XCircle) + .title(header) + .description(message.clone()) + .actions_slot( + h_flex() + .gap_0p5() + .child(self.render_retry_button(thread)) + .child(self.create_copy_button(message_with_header)), ) + .dismiss_action(self.dismiss_error_button(thread, cx)) .into_any_element() } @@ -3195,60 +3355,39 @@ impl AgentPanel { message: SharedString, can_enable_burn_mode: bool, thread: &Entity, - cx: &mut Context, ) -> AnyElement { - let icon = Icon::new(IconName::XCircle) - .size(IconSize::Small) - .color(Color::Error); - - let retry_button = Button::new("retry", "Retry") - .icon(IconName::RotateCw) - .icon_position(IconPosition::Start) - .icon_size(IconSize::Small) - .label_size(LabelSize::Small) - .on_click({ - let thread = thread.clone(); - move |_, window, cx| { - thread.update(cx, |thread, cx| { - thread.clear_last_error(); - thread.thread().update(cx, |thread, cx| { - thread.retry_last_completion(Some(window.window_handle()), cx); - }); - }); - } - }); - - let mut callout = Callout::new() - .icon(icon) + Callout::new() + .severity(Severity::Error) .title("Error") .description(message.clone()) - .bg_color(self.error_callout_bg(cx)) - .primary_action(retry_button); - - if can_enable_burn_mode { - let burn_mode_button = Button::new("enable_burn_retry", "Enable Burn Mode and Retry") - .icon(IconName::ZedBurnMode) - .icon_position(IconPosition::Start) - .icon_size(IconSize::Small) - .label_size(LabelSize::Small) - .on_click({ - let thread = thread.clone(); - move |_, window, cx| { - thread.update(cx, |thread, cx| { - thread.clear_last_error(); - thread.thread().update(cx, |thread, cx| { - thread.enable_burn_mode_and_retry(Some(window.window_handle()), cx); - }); - }); - } - }); - callout = callout.secondary_action(burn_mode_button); - } - - div() - .border_t_1() - .border_color(cx.theme().colors().border) - .child(callout) + .actions_slot( + h_flex() + .gap_0p5() + .when(can_enable_burn_mode, |this| { + this.child( + Button::new("enable_burn_retry", "Enable Burn Mode and Retry") + .icon(IconName::ZedBurnMode) + .icon_position(IconPosition::Start) + .icon_size(IconSize::Small) + .label_size(LabelSize::Small) + .on_click({ + let thread = thread.clone(); + move |_, window, cx| { + thread.update(cx, |thread, cx| { + thread.clear_last_error(); + thread.thread().update(cx, |thread, cx| { + thread.enable_burn_mode_and_retry( + Some(window.window_handle()), + cx, + ); + }); + }); + } + }), + ) + }) + .child(self.render_retry_button(thread)), + ) .into_any_element() } @@ -3327,9 +3466,9 @@ impl AgentPanel { .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| { let tasks = paths .paths() - .into_iter() + .iter() .map(|path| { - Workspace::project_path_for_path(this.project.clone(), &path, false, cx) + Workspace::project_path_for_path(this.project.clone(), path, false, cx) }) .collect::>(); cx.spawn_in(window, async move |this, cx| { @@ -3503,7 +3642,6 @@ impl Render for AgentPanel { message, can_enable_burn_mode, thread, - cx, ), }) .into_any(), @@ -3517,7 +3655,13 @@ impl Render for AgentPanel { ActiveView::ExternalAgentThread { thread_view, .. } => parent .child(thread_view.clone()) .child(self.render_drag_target(cx)), - ActiveView::History => parent.child(self.history.clone()), + ActiveView::History => { + if cx.has_flag::() { + parent.child(self.acp_history.clone()) + } else { + parent.child(self.history.clone()) + } + } ActiveView::TextThread { context_editor, buffer_search_bar, @@ -3531,16 +3675,13 @@ impl Render for AgentPanel { if !self.should_render_onboarding(cx) && let Some(err) = configuration_error.as_ref() { - this.child( - div().bg(cx.theme().colors().editor_background).p_2().child( - self.render_configuration_error( - err, - &self.focus_handle(cx), - window, - cx, - ), - ), - ) + this.child(self.render_configuration_error( + true, + err, + &self.focus_handle(cx), + window, + cx, + )) } else { this } @@ -3599,7 +3740,7 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist { let text_thread_store = None; let context_store = cx.new(|_| ContextStore::new(project.clone(), None)); assistant.assist( - &prompt_editor, + prompt_editor, self.workspace.clone(), context_store, project, diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index ce1c2203bf..a1dbc77084 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -156,11 +156,15 @@ enum ExternalAgent { } impl ExternalAgent { - pub fn server(&self, fs: Arc) -> Rc { + pub fn server( + &self, + fs: Arc, + history: Entity, + ) -> Rc { match self { ExternalAgent::Gemini => Rc::new(agent_servers::Gemini), ExternalAgent::ClaudeCode => Rc::new(agent_servers::ClaudeCode), - ExternalAgent::NativeAgent => Rc::new(agent2::NativeAgentServer::new(fs)), + ExternalAgent::NativeAgent => Rc::new(agent2::NativeAgentServer::new(fs, history)), } } } @@ -320,7 +324,7 @@ fn init_language_model_settings(cx: &mut App) { cx.subscribe( &LanguageModelRegistry::global(cx), |_, event: &language_model::Event, cx| match event { - language_model::Event::ProviderStateChanged + language_model::Event::ProviderStateChanged(_) | language_model::Event::AddedProvider(_) | language_model::Event::RemovedProvider(_) => { update_active_language_model_from_settings(cx); diff --git a/crates/agent_ui/src/buffer_codegen.rs b/crates/agent_ui/src/buffer_codegen.rs index 615142b73d..ff5e9362dd 100644 --- a/crates/agent_ui/src/buffer_codegen.rs +++ b/crates/agent_ui/src/buffer_codegen.rs @@ -352,12 +352,12 @@ impl CodegenAlternative { event: &multi_buffer::Event, cx: &mut Context, ) { - if let multi_buffer::Event::TransactionUndone { transaction_id } = event { - if self.transformation_transaction_id == Some(*transaction_id) { - self.transformation_transaction_id = None; - self.generation = Task::ready(()); - cx.emit(CodegenEvent::Undone); - } + if let multi_buffer::Event::TransactionUndone { transaction_id } = event + && self.transformation_transaction_id == Some(*transaction_id) + { + self.transformation_transaction_id = None; + self.generation = Task::ready(()); + cx.emit(CodegenEvent::Undone); } } @@ -388,7 +388,7 @@ impl CodegenAlternative { } else { let request = self.build_request(&model, user_prompt, cx)?; cx.spawn(async move |_, cx| { - Ok(model.stream_completion_text(request.await, &cx).await?) + Ok(model.stream_completion_text(request.await, cx).await?) }) .boxed_local() }; @@ -447,7 +447,7 @@ impl CodegenAlternative { } }); - let temperature = AgentSettings::temperature_for_model(&model, cx); + let temperature = AgentSettings::temperature_for_model(model, cx); Ok(cx.spawn(async move |_cx| { let mut request_message = LanguageModelRequestMessage { @@ -576,38 +576,34 @@ impl CodegenAlternative { let mut lines = chunk.split('\n').peekable(); while let Some(line) = lines.next() { new_text.push_str(line); - if line_indent.is_none() { - if let Some(non_whitespace_ch_ix) = + if line_indent.is_none() + && let Some(non_whitespace_ch_ix) = new_text.find(|ch: char| !ch.is_whitespace()) - { - line_indent = Some(non_whitespace_ch_ix); - base_indent = base_indent.or(line_indent); + { + line_indent = Some(non_whitespace_ch_ix); + base_indent = base_indent.or(line_indent); - let line_indent = line_indent.unwrap(); - let base_indent = base_indent.unwrap(); - let indent_delta = - line_indent as i32 - base_indent as i32; - let mut corrected_indent_len = cmp::max( - 0, - suggested_line_indent.len as i32 + indent_delta, - ) - as usize; - if first_line { - corrected_indent_len = corrected_indent_len - .saturating_sub( - selection_start.column as usize, - ); - } - - let indent_char = suggested_line_indent.char(); - let mut indent_buffer = [0; 4]; - let indent_str = - indent_char.encode_utf8(&mut indent_buffer); - new_text.replace_range( - ..line_indent, - &indent_str.repeat(corrected_indent_len), - ); + let line_indent = line_indent.unwrap(); + let base_indent = base_indent.unwrap(); + let indent_delta = line_indent as i32 - base_indent as i32; + let mut corrected_indent_len = cmp::max( + 0, + suggested_line_indent.len as i32 + indent_delta, + ) + as usize; + if first_line { + corrected_indent_len = corrected_indent_len + .saturating_sub(selection_start.column as usize); } + + let indent_char = suggested_line_indent.char(); + let mut indent_buffer = [0; 4]; + let indent_str = + indent_char.encode_utf8(&mut indent_buffer); + new_text.replace_range( + ..line_indent, + &indent_str.repeat(corrected_indent_len), + ); } if line_indent.is_some() { @@ -1028,7 +1024,7 @@ where chunk.push('\n'); } - chunk.push_str(&line); + chunk.push_str(line); } consumed += line.len(); diff --git a/crates/agent_ui/src/context_picker.rs b/crates/agent_ui/src/context_picker.rs index 131023d249..0b4568dc87 100644 --- a/crates/agent_ui/src/context_picker.rs +++ b/crates/agent_ui/src/context_picker.rs @@ -385,12 +385,11 @@ impl ContextPicker { } pub fn select_first(&mut self, window: &mut Window, cx: &mut Context) { - match &self.mode { - ContextPickerState::Default(entity) => entity.update(cx, |entity, cx| { + // Other variants already select their first entry on open automatically + if let ContextPickerState::Default(entity) = &self.mode { + entity.update(cx, |entity, cx| { entity.select_first(&Default::default(), window, cx) - }), - // Other variants already select their first entry on open automatically - _ => {} + }) } } @@ -610,9 +609,7 @@ pub(crate) fn available_context_picker_entries( .read(cx) .active_item(cx) .and_then(|item| item.downcast::()) - .map_or(false, |editor| { - editor.update(cx, |editor, cx| editor.has_non_empty_selection(cx)) - }); + .is_some_and(|editor| editor.update(cx, |editor, cx| editor.has_non_empty_selection(cx))); if has_selection { entries.push(ContextPickerEntry::Action( ContextPickerAction::AddSelections, @@ -680,7 +677,7 @@ pub(crate) fn recent_context_picker_entries( .filter(|(_, abs_path)| { abs_path .as_ref() - .map_or(true, |path| !exclude_paths.contains(path.as_path())) + .is_none_or(|path| !exclude_paths.contains(path.as_path())) }) .take(4) .filter_map(|(project_path, _)| { diff --git a/crates/agent_ui/src/context_picker/completion_provider.rs b/crates/agent_ui/src/context_picker/completion_provider.rs index 962c0df03d..747ec46e0a 100644 --- a/crates/agent_ui/src/context_picker/completion_provider.rs +++ b/crates/agent_ui/src/context_picker/completion_provider.rs @@ -728,11 +728,11 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); let mut label = CodeLabel::default(); - label.push_str(&file_name, None); + label.push_str(file_name, None); label.push_str(" ", None); if let Some(directory) = directory { - label.push_str(&directory, comment_id); + label.push_str(directory, comment_id); } label.filter_range = 0..label.text().len(); @@ -1020,7 +1020,7 @@ impl MentionCompletion { && line .chars() .nth(last_mention_start - 1) - .map_or(false, |c| !c.is_whitespace()) + .is_some_and(|c| !c.is_whitespace()) { return None; } diff --git a/crates/agent_ui/src/context_picker/fetch_context_picker.rs b/crates/agent_ui/src/context_picker/fetch_context_picker.rs index 8ff68a8365..dd558b2a1c 100644 --- a/crates/agent_ui/src/context_picker/fetch_context_picker.rs +++ b/crates/agent_ui/src/context_picker/fetch_context_picker.rs @@ -226,9 +226,10 @@ impl PickerDelegate for FetchContextPickerDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { - let added = self.context_store.upgrade().map_or(false, |context_store| { - context_store.read(cx).includes_url(&self.url) - }); + let added = self + .context_store + .upgrade() + .is_some_and(|context_store| context_store.read(cx).includes_url(&self.url)); Some( ListItem::new(ix) diff --git a/crates/agent_ui/src/context_picker/file_context_picker.rs b/crates/agent_ui/src/context_picker/file_context_picker.rs index eaf9ed16d6..6c224caf4c 100644 --- a/crates/agent_ui/src/context_picker/file_context_picker.rs +++ b/crates/agent_ui/src/context_picker/file_context_picker.rs @@ -239,9 +239,7 @@ pub(crate) fn search_files( PathMatchCandidateSet { snapshot: worktree.snapshot(), - include_ignored: worktree - .root_entry() - .map_or(false, |entry| entry.is_ignored), + include_ignored: worktree.root_entry().is_some_and(|entry| entry.is_ignored), include_root_name: true, candidates: project::Candidates::Entries, } @@ -315,7 +313,7 @@ pub fn render_file_context_entry( context_store: WeakEntity, cx: &App, ) -> Stateful
{ - let (file_name, directory) = extract_file_name_and_directory(&path, path_prefix); + let (file_name, directory) = extract_file_name_and_directory(path, path_prefix); let added = context_store.upgrade().and_then(|context_store| { let project_path = ProjectPath { @@ -334,7 +332,7 @@ pub fn render_file_context_entry( let file_icon = if is_directory { FileIcons::get_folder_icon(false, cx) } else { - FileIcons::get_icon(&path, cx) + FileIcons::get_icon(path, cx) } .map(Icon::from_path) .unwrap_or_else(|| Icon::new(IconName::File)); diff --git a/crates/agent_ui/src/context_picker/rules_context_picker.rs b/crates/agent_ui/src/context_picker/rules_context_picker.rs index 8ce821cfaa..f3982f61cb 100644 --- a/crates/agent_ui/src/context_picker/rules_context_picker.rs +++ b/crates/agent_ui/src/context_picker/rules_context_picker.rs @@ -159,7 +159,7 @@ pub fn render_thread_context_entry( context_store: WeakEntity, cx: &mut App, ) -> Div { - let added = context_store.upgrade().map_or(false, |context_store| { + let added = context_store.upgrade().is_some_and(|context_store| { context_store .read(cx) .includes_user_rules(user_rules.prompt_id) diff --git a/crates/agent_ui/src/context_picker/symbol_context_picker.rs b/crates/agent_ui/src/context_picker/symbol_context_picker.rs index 05e77deece..b00d4e3693 100644 --- a/crates/agent_ui/src/context_picker/symbol_context_picker.rs +++ b/crates/agent_ui/src/context_picker/symbol_context_picker.rs @@ -289,12 +289,12 @@ pub(crate) fn search_symbols( .iter() .enumerate() .map(|(id, symbol)| { - StringMatchCandidate::new(id, &symbol.label.filter_text()) + StringMatchCandidate::new(id, symbol.label.filter_text()) }) .partition(|candidate| { project .entry_for_path(&symbols[candidate.id].path, cx) - .map_or(false, |e| !e.is_ignored) + .is_some_and(|e| !e.is_ignored) }) }) .log_err() diff --git a/crates/agent_ui/src/context_picker/thread_context_picker.rs b/crates/agent_ui/src/context_picker/thread_context_picker.rs index 15cc731f8f..66654f3d8c 100644 --- a/crates/agent_ui/src/context_picker/thread_context_picker.rs +++ b/crates/agent_ui/src/context_picker/thread_context_picker.rs @@ -167,7 +167,7 @@ impl PickerDelegate for ThreadContextPickerDelegate { return; }; let open_thread_task = - thread_store.update(cx, |this, cx| this.open_thread(&id, window, cx)); + thread_store.update(cx, |this, cx| this.open_thread(id, window, cx)); cx.spawn(async move |this, cx| { let thread = open_thread_task.await?; @@ -236,12 +236,10 @@ pub fn render_thread_context_entry( let is_added = match entry { ThreadContextEntry::Thread { id, .. } => context_store .upgrade() - .map_or(false, |ctx_store| ctx_store.read(cx).includes_thread(&id)), - ThreadContextEntry::Context { path, .. } => { - context_store.upgrade().map_or(false, |ctx_store| { - ctx_store.read(cx).includes_text_thread(path) - }) - } + .is_some_and(|ctx_store| ctx_store.read(cx).includes_thread(id)), + ThreadContextEntry::Context { path, .. } => context_store + .upgrade() + .is_some_and(|ctx_store| ctx_store.read(cx).includes_text_thread(path)), }; h_flex() @@ -338,7 +336,7 @@ pub(crate) fn search_threads( let candidates = threads .iter() .enumerate() - .map(|(id, (_, thread))| StringMatchCandidate::new(id, &thread.title())) + .map(|(id, (_, thread))| StringMatchCandidate::new(id, thread.title())) .collect::>(); let matches = fuzzy::match_strings( &candidates, diff --git a/crates/agent_ui/src/context_strip.rs b/crates/agent_ui/src/context_strip.rs index 369964f165..d25d7d3544 100644 --- a/crates/agent_ui/src/context_strip.rs +++ b/crates/agent_ui/src/context_strip.rs @@ -145,7 +145,7 @@ impl ContextStrip { } let file_name = active_buffer.file()?.file_name(cx); - let icon_path = FileIcons::get_icon(&Path::new(&file_name), cx); + let icon_path = FileIcons::get_icon(Path::new(&file_name), cx); Some(SuggestedContext::File { name: file_name.to_string_lossy().into_owned().into(), buffer: active_buffer_entity.downgrade(), @@ -368,16 +368,16 @@ impl ContextStrip { _window: &mut Window, cx: &mut Context, ) { - if let Some(suggested) = self.suggested_context(cx) { - if self.is_suggested_focused(&self.added_contexts(cx)) { - self.add_suggested_context(&suggested, cx); - } + if let Some(suggested) = self.suggested_context(cx) + && self.is_suggested_focused(&self.added_contexts(cx)) + { + self.add_suggested_context(&suggested, cx); } } fn add_suggested_context(&mut self, suggested: &SuggestedContext, cx: &mut Context) { self.context_store.update(cx, |context_store, cx| { - context_store.add_suggested_context(&suggested, cx) + context_store.add_suggested_context(suggested, cx) }); cx.notify(); } diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index bbd3595805..90302236fb 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -182,13 +182,13 @@ impl InlineAssistant { match event { workspace::Event::UserSavedItem { item, .. } => { // When the user manually saves an editor, automatically accepts all finished transformations. - if let Some(editor) = item.upgrade().and_then(|item| item.act_as::(cx)) { - if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { - for assist_id in editor_assists.assist_ids.clone() { - let assist = &self.assists[&assist_id]; - if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { - self.finish_assist(assist_id, false, window, cx) - } + if let Some(editor) = item.upgrade().and_then(|item| item.act_as::(cx)) + && let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) + { + for assist_id in editor_assists.assist_ids.clone() { + let assist = &self.assists[&assist_id]; + if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { + self.finish_assist(assist_id, false, window, cx) } } } @@ -342,13 +342,11 @@ impl InlineAssistant { ) .await .ok(); - if let Some(answer) = answer { - if answer == 0 { - cx.update(|window, cx| { - window.dispatch_action(Box::new(OpenSettings), cx) - }) + if let Some(answer) = answer + && answer == 0 + { + cx.update(|window, cx| window.dispatch_action(Box::new(OpenSettings), cx)) .ok(); - } } anyhow::Ok(()) }) @@ -435,11 +433,11 @@ impl InlineAssistant { } } - if let Some(prev_selection) = selections.last_mut() { - if selection.start <= prev_selection.end { - prev_selection.end = selection.end; - continue; - } + if let Some(prev_selection) = selections.last_mut() + && selection.start <= prev_selection.end + { + prev_selection.end = selection.end; + continue; } let latest_selection = newest_selection.get_or_insert_with(|| selection.clone()); @@ -526,9 +524,9 @@ impl InlineAssistant { if assist_to_focus.is_none() { let focus_assist = if newest_selection.reversed { - range.start.to_point(&snapshot) == newest_selection.start + range.start.to_point(snapshot) == newest_selection.start } else { - range.end.to_point(&snapshot) == newest_selection.end + range.end.to_point(snapshot) == newest_selection.end }; if focus_assist { assist_to_focus = Some(assist_id); @@ -550,7 +548,7 @@ impl InlineAssistant { let editor_assists = self .assists_by_editor .entry(editor.downgrade()) - .or_insert_with(|| EditorInlineAssists::new(&editor, window, cx)); + .or_insert_with(|| EditorInlineAssists::new(editor, window, cx)); let mut assist_group = InlineAssistGroup::new(); for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists { let codegen = prompt_editor.read(cx).codegen().clone(); @@ -649,7 +647,7 @@ impl InlineAssistant { let editor_assists = self .assists_by_editor .entry(editor.downgrade()) - .or_insert_with(|| EditorInlineAssists::new(&editor, window, cx)); + .or_insert_with(|| EditorInlineAssists::new(editor, window, cx)); let mut assist_group = InlineAssistGroup::new(); self.assists.insert( @@ -985,14 +983,13 @@ impl InlineAssistant { EditorEvent::SelectionsChanged { .. } => { for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; - if let Some(decorations) = assist.decorations.as_ref() { - if decorations + if let Some(decorations) = assist.decorations.as_ref() + && decorations .prompt_editor .focus_handle(cx) .is_focused(window) - { - return; - } + { + return; } } @@ -1123,7 +1120,7 @@ impl InlineAssistant { if editor_assists .scroll_lock .as_ref() - .map_or(false, |lock| lock.assist_id == assist_id) + .is_some_and(|lock| lock.assist_id == assist_id) { editor_assists.scroll_lock = None; } @@ -1503,20 +1500,18 @@ impl InlineAssistant { window: &mut Window, cx: &mut App, ) -> Option { - if let Some(terminal_panel) = workspace.panel::(cx) { - if terminal_panel + if let Some(terminal_panel) = workspace.panel::(cx) + && terminal_panel .read(cx) .focus_handle(cx) .contains_focused(window, cx) - { - if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| { - pane.read(cx) - .active_item() - .and_then(|t| t.downcast::()) - }) { - return Some(InlineAssistTarget::Terminal(terminal_view)); - } - } + && let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| { + pane.read(cx) + .active_item() + .and_then(|t| t.downcast::()) + }) + { + return Some(InlineAssistTarget::Terminal(terminal_view)); } let context_editor = agent_panel @@ -1741,22 +1736,20 @@ impl InlineAssist { return; }; - if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) { - if assist.decorations.is_none() { - if let Some(workspace) = assist.workspace.upgrade() { - let error = format!("Inline assistant error: {}", error); - workspace.update(cx, |workspace, cx| { - struct InlineAssistantError; + if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) + && assist.decorations.is_none() + && let Some(workspace) = assist.workspace.upgrade() + { + let error = format!("Inline assistant error: {}", error); + workspace.update(cx, |workspace, cx| { + struct InlineAssistantError; - let id = - NotificationId::composite::( - assist_id.0, - ); + let id = NotificationId::composite::( + assist_id.0, + ); - workspace.show_toast(Toast::new(id, error), cx); - }) - } - } + workspace.show_toast(Toast::new(id, error), cx); + }) } if assist.decorations.is_none() { @@ -1821,18 +1814,18 @@ impl CodeActionProvider for AssistantCodeActionProvider { has_diagnostics = true; } if has_diagnostics { - if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) { - if let Some(symbol) = symbols_containing_start.last() { - range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); - range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); - } + if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) + && let Some(symbol) = symbols_containing_start.last() + { + range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); + range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); } - if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) { - if let Some(symbol) = symbols_containing_end.last() { - range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); - range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); - } + if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) + && let Some(symbol) = symbols_containing_end.last() + { + range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); + range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); } Task::ready(Ok(vec![CodeAction { diff --git a/crates/agent_ui/src/inline_prompt_editor.rs b/crates/agent_ui/src/inline_prompt_editor.rs index e6fca16984..5608143464 100644 --- a/crates/agent_ui/src/inline_prompt_editor.rs +++ b/crates/agent_ui/src/inline_prompt_editor.rs @@ -75,7 +75,7 @@ impl Render for PromptEditor { let codegen = codegen.read(cx); if codegen.alternative_count(cx) > 1 { - buttons.push(self.render_cycle_controls(&codegen, cx)); + buttons.push(self.render_cycle_controls(codegen, cx)); } let editor_margins = editor_margins.lock(); @@ -345,7 +345,7 @@ impl PromptEditor { let prompt = self.editor.read(cx).text(cx); if self .prompt_history_ix - .map_or(true, |ix| self.prompt_history[ix] != prompt) + .is_none_or(|ix| self.prompt_history[ix] != prompt) { self.prompt_history_ix.take(); self.pending_prompt = prompt; diff --git a/crates/agent_ui/src/language_model_selector.rs b/crates/agent_ui/src/language_model_selector.rs index bb8514a224..845540979a 100644 --- a/crates/agent_ui/src/language_model_selector.rs +++ b/crates/agent_ui/src/language_model_selector.rs @@ -104,7 +104,7 @@ impl LanguageModelPickerDelegate { window, |picker, _, event, window, cx| { match event { - language_model::Event::ProviderStateChanged + language_model::Event::ProviderStateChanged(_) | language_model::Event::AddedProvider(_) | language_model::Event::RemovedProvider(_) => { let query = picker.query(cx); @@ -296,7 +296,7 @@ impl ModelMatcher { pub fn fuzzy_search(&self, query: &str) -> Vec { let mut matches = self.bg_executor.block(match_strings( &self.candidates, - &query, + query, false, true, 100, diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index d6c9a778a6..f70d10c1ae 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -117,7 +117,7 @@ pub(crate) fn create_editor( let mut editor = Editor::new( editor::EditorMode::AutoHeight { min_lines, - max_lines: max_lines, + max_lines, }, buffer, None, @@ -156,7 +156,7 @@ impl ProfileProvider for Entity { fn profiles_supported(&self, cx: &App) -> bool { self.read(cx) .configured_model() - .map_or(false, |model| model.model.supports_tools()) + .is_some_and(|model| model.model.supports_tools()) } fn profile_id(&self, cx: &App) -> AgentProfileId { @@ -215,9 +215,10 @@ impl MessageEditor { let subscriptions = vec![ cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event), - cx.subscribe(&editor, |this, _, event, cx| match event { - EditorEvent::BufferEdited => this.handle_message_changed(cx), - _ => {} + cx.subscribe(&editor, |this, _, event: &EditorEvent, cx| { + if event == &EditorEvent::BufferEdited { + this.handle_message_changed(cx) + } }), cx.observe(&context_store, |this, _, cx| { // When context changes, reload it for token counting. @@ -690,11 +691,7 @@ impl MessageEditor { .as_ref() .map(|model| { self.incompatible_tools_state.update(cx, |state, cx| { - state - .incompatible_tools(&model.model, cx) - .iter() - .cloned() - .collect::>() + state.incompatible_tools(&model.model, cx).to_vec() }) }) .unwrap_or_default(); @@ -1136,7 +1133,7 @@ impl MessageEditor { ) .when(is_edit_changes_expanded, |parent| { parent.child( - v_flex().children(changed_buffers.into_iter().enumerate().flat_map( + v_flex().children(changed_buffers.iter().enumerate().flat_map( |(index, (buffer, _diff))| { let file = buffer.read(cx).file()?; let path = file.path(); @@ -1166,7 +1163,7 @@ impl MessageEditor { .buffer_font(cx) }); - let file_icon = FileIcons::get_icon(&path, cx) + let file_icon = FileIcons::get_icon(path, cx) .map(Icon::from_path) .map(|icon| icon.color(Color::Muted).size(IconSize::Small)) .unwrap_or_else(|| { @@ -1293,7 +1290,7 @@ impl MessageEditor { self.thread .read(cx) .configured_model() - .map_or(false, |model| model.provider.id() == ZED_CLOUD_PROVIDER_ID) + .is_some_and(|model| model.provider.id() == ZED_CLOUD_PROVIDER_ID) } fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context) -> Option
{ @@ -1323,14 +1320,10 @@ impl MessageEditor { token_usage_ratio: TokenUsageRatio, cx: &mut Context, ) -> Option
{ - let icon = if token_usage_ratio == TokenUsageRatio::Exceeded { - Icon::new(IconName::Close) - .color(Color::Error) - .size(IconSize::XSmall) + let (icon, severity) = if token_usage_ratio == TokenUsageRatio::Exceeded { + (IconName::Close, Severity::Error) } else { - Icon::new(IconName::Warning) - .color(Color::Warning) - .size(IconSize::XSmall) + (IconName::Warning, Severity::Warning) }; let title = if token_usage_ratio == TokenUsageRatio::Exceeded { @@ -1345,30 +1338,34 @@ impl MessageEditor { "To continue, start a new thread from a summary." }; - let mut callout = Callout::new() + let callout = Callout::new() .line_height(line_height) + .severity(severity) .icon(icon) .title(title) .description(description) - .primary_action( - Button::new("start-new-thread", "Start New Thread") - .label_size(LabelSize::Small) - .on_click(cx.listener(|this, _, window, cx| { - let from_thread_id = Some(this.thread.read(cx).id().clone()); - window.dispatch_action(Box::new(NewThread { from_thread_id }), cx); - })), + .actions_slot( + h_flex() + .gap_0p5() + .when(self.is_using_zed_provider(cx), |this| { + this.child( + IconButton::new("burn-mode-callout", IconName::ZedBurnMode) + .icon_size(IconSize::XSmall) + .on_click(cx.listener(|this, _event, window, cx| { + this.toggle_burn_mode(&ToggleBurnMode, window, cx); + })), + ) + }) + .child( + Button::new("start-new-thread", "Start New Thread") + .label_size(LabelSize::Small) + .on_click(cx.listener(|this, _, window, cx| { + let from_thread_id = Some(this.thread.read(cx).id().clone()); + window.dispatch_action(Box::new(NewThread { from_thread_id }), cx); + })), + ), ); - if self.is_using_zed_provider(cx) { - callout = callout.secondary_action( - IconButton::new("burn-mode-callout", IconName::ZedBurnMode) - .icon_size(IconSize::XSmall) - .on_click(cx.listener(|this, _event, window, cx| { - this.toggle_burn_mode(&ToggleBurnMode, window, cx); - })), - ); - } - Some( div() .border_t_1() @@ -1446,7 +1443,7 @@ impl MessageEditor { let message_text = editor.read(cx).text(cx); if message_text.is_empty() - && loaded_context.map_or(true, |loaded_context| loaded_context.is_empty()) + && loaded_context.is_none_or(|loaded_context| loaded_context.is_empty()) { return None; } @@ -1559,9 +1556,8 @@ impl ContextCreasesAddon { cx: &mut Context, ) { self.creases.entry(key).or_default().extend(creases); - self._subscription = Some(cx.subscribe( - &context_store, - |editor, _, event, cx| match event { + self._subscription = Some( + cx.subscribe(context_store, |editor, _, event, cx| match event { ContextStoreEvent::ContextRemoved(key) => { let Some(this) = editor.addon_mut::() else { return; @@ -1581,8 +1577,8 @@ impl ContextCreasesAddon { editor.edit(ranges.into_iter().zip(replacement_texts), cx); cx.notify(); } - }, - )) + }), + ) } pub fn into_inner(self) -> HashMap> { @@ -1610,7 +1606,8 @@ pub fn extract_message_creases( .collect::>(); // Filter the addon's list of creases based on what the editor reports, // since the addon might have removed creases in it. - let creases = editor.display_map.update(cx, |display_map, cx| { + + editor.display_map.update(cx, |display_map, cx| { display_map .snapshot(cx) .crease_snapshot @@ -1634,8 +1631,7 @@ pub fn extract_message_creases( } }) .collect() - }); - creases + }) } impl EventEmitter for MessageEditor {} diff --git a/crates/agent_ui/src/slash_command_picker.rs b/crates/agent_ui/src/slash_command_picker.rs index 678562e059..a6bb61510c 100644 --- a/crates/agent_ui/src/slash_command_picker.rs +++ b/crates/agent_ui/src/slash_command_picker.rs @@ -140,12 +140,10 @@ impl PickerDelegate for SlashCommandDelegate { ); ret.push(index - 1); } - } else { - if let SlashCommandEntry::Advert { .. } = command { - previous_is_advert = true; - if index != 0 { - ret.push(index - 1); - } + } else if let SlashCommandEntry::Advert { .. } = command { + previous_is_advert = true; + if index != 0 { + ret.push(index - 1); } } } @@ -214,7 +212,7 @@ impl PickerDelegate for SlashCommandDelegate { let mut label = format!("{}", info.name); if let Some(args) = info.args.as_ref().filter(|_| selected) { - label.push_str(&args); + label.push_str(args); } Label::new(label) .single_line() @@ -329,9 +327,7 @@ where }; let picker_view = cx.new(|cx| { - let picker = - Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into())); - picker + Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into())) }); let handle = self diff --git a/crates/agent_ui/src/terminal_codegen.rs b/crates/agent_ui/src/terminal_codegen.rs index 54f5b52f58..5a4a9d560a 100644 --- a/crates/agent_ui/src/terminal_codegen.rs +++ b/crates/agent_ui/src/terminal_codegen.rs @@ -48,7 +48,7 @@ impl TerminalCodegen { let prompt = prompt_task.await; let model_telemetry_id = model.telemetry_id(); let model_provider_id = model.provider_id(); - let response = model.stream_completion_text(prompt, &cx).await; + let response = model.stream_completion_text(prompt, cx).await; let generate = async { let message_id = response .as_ref() diff --git a/crates/agent_ui/src/terminal_inline_assistant.rs b/crates/agent_ui/src/terminal_inline_assistant.rs index bcbc308c99..3859863ebe 100644 --- a/crates/agent_ui/src/terminal_inline_assistant.rs +++ b/crates/agent_ui/src/terminal_inline_assistant.rs @@ -388,20 +388,20 @@ impl TerminalInlineAssistant { window: &mut Window, cx: &mut App, ) { - if let Some(assist) = self.assists.get_mut(&assist_id) { - if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() { - assist - .terminal - .update(cx, |terminal, cx| { - terminal.clear_block_below_cursor(cx); - let block = terminal_view::BlockProperties { - height, - render: Box::new(move |_| prompt_editor.clone().into_any_element()), - }; - terminal.set_block_below_cursor(block, window, cx); - }) - .log_err(); - } + if let Some(assist) = self.assists.get_mut(&assist_id) + && let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() + { + assist + .terminal + .update(cx, |terminal, cx| { + terminal.clear_block_below_cursor(cx); + let block = terminal_view::BlockProperties { + height, + render: Box::new(move |_| prompt_editor.clone().into_any_element()), + }; + terminal.set_block_below_cursor(block, window, cx); + }) + .log_err(); } } } @@ -450,23 +450,20 @@ impl TerminalInlineAssist { return; }; - if let CodegenStatus::Error(error) = &codegen.read(cx).status { - if assist.prompt_editor.is_none() { - if let Some(workspace) = assist.workspace.upgrade() { - let error = - format!("Terminal inline assistant error: {}", error); - workspace.update(cx, |workspace, cx| { - struct InlineAssistantError; + if let CodegenStatus::Error(error) = &codegen.read(cx).status + && assist.prompt_editor.is_none() + && let Some(workspace) = assist.workspace.upgrade() + { + let error = format!("Terminal inline assistant error: {}", error); + workspace.update(cx, |workspace, cx| { + struct InlineAssistantError; - let id = - NotificationId::composite::( - assist_id.0, - ); + let id = NotificationId::composite::( + assist_id.0, + ); - workspace.show_toast(Toast::new(id, error), cx); - }) - } - } + workspace.show_toast(Toast::new(id, error), cx); + }) } if assist.prompt_editor.is_none() { diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 8c1e163eca..b3f55ffc43 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -373,7 +373,7 @@ impl TextThreadEditor { .map(|default| default.provider); if provider .as_ref() - .map_or(false, |provider| provider.must_accept_terms(cx)) + .is_some_and(|provider| provider.must_accept_terms(cx)) { self.show_accept_terms = true; cx.notify(); @@ -457,7 +457,7 @@ impl TextThreadEditor { || snapshot .chars_at(newest_cursor) .next() - .map_or(false, |ch| ch != '\n') + .is_some_and(|ch| ch != '\n') { editor.move_to_end_of_line( &MoveToEndOfLine { @@ -540,7 +540,7 @@ impl TextThreadEditor { let context = self.context.read(cx); let sections = context .slash_command_output_sections() - .into_iter() + .iter() .filter(|section| section.is_valid(context.buffer().read(cx))) .cloned() .collect::>(); @@ -745,32 +745,27 @@ impl TextThreadEditor { ) { if let Some(invoked_slash_command) = self.context.read(cx).invoked_slash_command(&command_id) + && let InvokedSlashCommandStatus::Finished = invoked_slash_command.status { - if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status { - let run_commands_in_ranges = invoked_slash_command - .run_commands_in_ranges - .iter() - .cloned() - .collect::>(); - for range in run_commands_in_ranges { - let commands = self.context.update(cx, |context, cx| { - context.reparse(cx); - context - .pending_commands_for_range(range.clone(), cx) - .to_vec() - }); + let run_commands_in_ranges = invoked_slash_command.run_commands_in_ranges.clone(); + for range in run_commands_in_ranges { + let commands = self.context.update(cx, |context, cx| { + context.reparse(cx); + context + .pending_commands_for_range(range.clone(), cx) + .to_vec() + }); - for command in commands { - self.run_command( - command.source_range, - &command.name, - &command.arguments, - false, - self.workspace.clone(), - window, - cx, - ); - } + for command in commands { + self.run_command( + command.source_range, + &command.name, + &command.arguments, + false, + self.workspace.clone(), + window, + cx, + ); } } } @@ -1242,7 +1237,7 @@ impl TextThreadEditor { let mut new_blocks = vec![]; let mut block_index_to_message = vec![]; for message in self.context.read(cx).messages(cx) { - if let Some(_) = blocks_to_remove.remove(&message.id) { + if blocks_to_remove.remove(&message.id).is_some() { // This is an old message that we might modify. let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else { debug_assert!( @@ -1280,7 +1275,7 @@ impl TextThreadEditor { context_editor_view: &Entity, cx: &mut Context, ) -> Option<(String, bool)> { - const CODE_FENCE_DELIMITER: &'static str = "```"; + const CODE_FENCE_DELIMITER: &str = "```"; let context_editor = context_editor_view.read(cx).editor.clone(); context_editor.update(cx, |context_editor, cx| { @@ -2166,8 +2161,8 @@ impl TextThreadEditor { /// Returns the contents of the *outermost* fenced code block that contains the given offset. fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Option> { - const CODE_BLOCK_NODE: &'static str = "fenced_code_block"; - const CODE_BLOCK_CONTENT: &'static str = "code_fence_content"; + const CODE_BLOCK_NODE: &str = "fenced_code_block"; + const CODE_BLOCK_CONTENT: &str = "code_fence_content"; let layer = snapshot.syntax_layers().next()?; @@ -3134,7 +3129,7 @@ mod tests { let context_editor = window .update(&mut cx, |_, window, cx| { cx.new(|cx| { - let editor = TextThreadEditor::for_context( + TextThreadEditor::for_context( context.clone(), fs, workspace.downgrade(), @@ -3142,8 +3137,7 @@ mod tests { None, window, cx, - ); - editor + ) }) }) .unwrap(); diff --git a/crates/agent_ui/src/thread_history.rs b/crates/agent_ui/src/thread_history.rs index 4cf433c306..32c4de1d42 100644 --- a/crates/agent_ui/src/thread_history.rs +++ b/crates/agent_ui/src/thread_history.rs @@ -161,14 +161,13 @@ impl ThreadHistory { this.all_entries.len().saturating_sub(1), cx, ); - } else if let Some(prev_id) = previously_selected_entry { - if let Some(new_ix) = this + } else if let Some(prev_id) = previously_selected_entry + && let Some(new_ix) = this .all_entries .iter() .position(|probe| probe.id() == prev_id) - { - this.set_selected_entry_index(new_ix, cx); - } + { + this.set_selected_entry_index(new_ix, cx); } } SearchState::Searching { query, .. } | SearchState::Searched { query, .. } => { diff --git a/crates/agent_ui/src/tool_compatibility.rs b/crates/agent_ui/src/tool_compatibility.rs index d4e1da5bb0..046c0a4abc 100644 --- a/crates/agent_ui/src/tool_compatibility.rs +++ b/crates/agent_ui/src/tool_compatibility.rs @@ -14,13 +14,11 @@ pub struct IncompatibleToolsState { impl IncompatibleToolsState { pub fn new(thread: Entity, cx: &mut Context) -> Self { - let _tool_working_set_subscription = - cx.subscribe(&thread, |this, _, event, _| match event { - ThreadEvent::ProfileChanged => { - this.cache.clear(); - } - _ => {} - }); + let _tool_working_set_subscription = cx.subscribe(&thread, |this, _, event, _| { + if let ThreadEvent::ProfileChanged = event { + this.cache.clear(); + } + }); Self { cache: HashMap::default(), diff --git a/crates/agent_ui/src/ui/context_pill.rs b/crates/agent_ui/src/ui/context_pill.rs index 5dd57de244..4e33e151cd 100644 --- a/crates/agent_ui/src/ui/context_pill.rs +++ b/crates/agent_ui/src/ui/context_pill.rs @@ -353,7 +353,7 @@ impl AddedContext { name, parent, tooltip: Some(full_path_string), - icon_path: FileIcons::get_icon(&full_path, cx), + icon_path: FileIcons::get_icon(full_path, cx), status: ContextStatus::Ready, render_hover: None, handle: AgentContextHandle::File(handle), @@ -615,7 +615,7 @@ impl AddedContext { let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into(); let (name, parent) = extract_file_name_and_directory_from_full_path(full_path, &full_path_string); - let icon_path = FileIcons::get_icon(&full_path, cx); + let icon_path = FileIcons::get_icon(full_path, cx); (name, parent, icon_path) } else { ("Image".into(), None, None) @@ -706,7 +706,7 @@ impl ContextFileExcerpt { .and_then(|p| p.file_name()) .map(|n| n.to_string_lossy().into_owned().into()); - let icon_path = FileIcons::get_icon(&full_path, cx); + let icon_path = FileIcons::get_icon(full_path, cx); ContextFileExcerpt { file_name_and_range: file_name_and_range.into(), diff --git a/crates/agent_ui/src/ui/preview/usage_callouts.rs b/crates/agent_ui/src/ui/preview/usage_callouts.rs index eef878a9d1..29b12ea627 100644 --- a/crates/agent_ui/src/ui/preview/usage_callouts.rs +++ b/crates/agent_ui/src/ui/preview/usage_callouts.rs @@ -80,14 +80,10 @@ impl RenderOnce for UsageCallout { } }; - let icon = if is_limit_reached { - Icon::new(IconName::Close) - .color(Color::Error) - .size(IconSize::XSmall) + let (icon, severity) = if is_limit_reached { + (IconName::Close, Severity::Error) } else { - Icon::new(IconName::Warning) - .color(Color::Warning) - .size(IconSize::XSmall) + (IconName::Warning, Severity::Warning) }; div() @@ -95,10 +91,12 @@ impl RenderOnce for UsageCallout { .border_color(cx.theme().colors().border) .child( Callout::new() + .icon(icon) + .severity(severity) .icon(icon) .title(title) .description(message) - .primary_action( + .actions_slot( Button::new("upgrade", button_text) .label_size(LabelSize::Small) .on_click(move |_, _, cx| { diff --git a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs index b55ad4c895..0a34a29068 100644 --- a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs +++ b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs @@ -11,7 +11,7 @@ impl ApiKeysWithProviders { cx.subscribe( &LanguageModelRegistry::global(cx), |this: &mut Self, _registry, event: &language_model::Event, cx| match event { - language_model::Event::ProviderStateChanged + language_model::Event::ProviderStateChanged(_) | language_model::Event::AddedProvider(_) | language_model::Event::RemovedProvider(_) => { this.configured_providers = Self::compute_configured_providers(cx) diff --git a/crates/ai_onboarding/src/agent_panel_onboarding_content.rs b/crates/ai_onboarding/src/agent_panel_onboarding_content.rs index f1629eeff8..23810b74f3 100644 --- a/crates/ai_onboarding/src/agent_panel_onboarding_content.rs +++ b/crates/ai_onboarding/src/agent_panel_onboarding_content.rs @@ -25,7 +25,7 @@ impl AgentPanelOnboarding { cx.subscribe( &LanguageModelRegistry::global(cx), |this: &mut Self, _registry, event: &language_model::Event, cx| match event { - language_model::Event::ProviderStateChanged + language_model::Event::ProviderStateChanged(_) | language_model::Event::AddedProvider(_) | language_model::Event::RemovedProvider(_) => { this.configured_providers = Self::compute_available_providers(cx) diff --git a/crates/ai_onboarding/src/ai_onboarding.rs b/crates/ai_onboarding/src/ai_onboarding.rs index 75177d4bd2..717abebfd1 100644 --- a/crates/ai_onboarding/src/ai_onboarding.rs +++ b/crates/ai_onboarding/src/ai_onboarding.rs @@ -332,17 +332,25 @@ impl ZedAiOnboarding { .mb_2(), ) .child(plan_definitions.pro_plan(false)) - .child( - Button::new("pro", "Continue with Zed Pro") - .full_width() - .style(ButtonStyle::Outlined) - .on_click({ - let callback = self.continue_with_zed_ai.clone(); - move |_, window, cx| { - telemetry::event!("Banner Dismissed", source = "AI Onboarding"); - callback(window, cx) - } - }), + .when_some( + self.dismiss_onboarding.as_ref(), + |this, dismiss_callback| { + let callback = dismiss_callback.clone(); + this.child( + h_flex().absolute().top_0().right_0().child( + IconButton::new("dismiss_onboarding", IconName::Close) + .icon_size(IconSize::Small) + .tooltip(Tooltip::text("Dismiss")) + .on_click(move |_, window, cx| { + telemetry::event!( + "Banner Dismissed", + source = "AI Onboarding", + ); + callback(window, cx) + }), + ), + ) + }, ) .into_any_element() } diff --git a/crates/ai_onboarding/src/young_account_banner.rs b/crates/ai_onboarding/src/young_account_banner.rs index 54f563e4aa..ed9a6b3b35 100644 --- a/crates/ai_onboarding/src/young_account_banner.rs +++ b/crates/ai_onboarding/src/young_account_banner.rs @@ -17,6 +17,6 @@ impl RenderOnce for YoungAccountBanner { div() .max_w_full() .my_1() - .child(Banner::new().severity(ui::Severity::Warning).child(label)) + .child(Banner::new().severity(Severity::Warning).child(label)) } } diff --git a/crates/askpass/src/askpass.rs b/crates/askpass/src/askpass.rs index f085a2be72..9e84a9fed0 100644 --- a/crates/askpass/src/askpass.rs +++ b/crates/askpass/src/askpass.rs @@ -177,11 +177,11 @@ impl AskPassSession { _ = askpass_opened_rx.fuse() => { // Note: this await can only resolve after we are dropped. askpass_kill_master_rx.await.ok(); - return AskPassResult::CancelledByUser + AskPassResult::CancelledByUser } _ = futures::FutureExt::fuse(smol::Timer::after(connection_timeout)) => { - return AskPassResult::Timedout + AskPassResult::Timedout } } } @@ -215,7 +215,7 @@ pub fn main(socket: &str) { } #[cfg(target_os = "windows")] - while buffer.last().map_or(false, |&b| b == b'\n' || b == b'\r') { + while buffer.last().is_some_and(|&b| b == b'\n' || b == b'\r') { buffer.pop(); } if buffer.last() != Some(&b'\0') { diff --git a/crates/assistant_context/src/assistant_context.rs b/crates/assistant_context/src/assistant_context.rs index 557f9592e4..4d0bfae444 100644 --- a/crates/assistant_context/src/assistant_context.rs +++ b/crates/assistant_context/src/assistant_context.rs @@ -590,17 +590,16 @@ impl From<&Message> for MessageMetadata { impl MessageMetadata { pub fn is_cache_valid(&self, buffer: &BufferSnapshot, range: &Range) -> bool { - let result = match &self.cache { + match &self.cache { Some(MessageCacheMetadata { cached_at, .. }) => !buffer.has_edits_since_in_range( - &cached_at, + cached_at, Range { start: buffer.anchor_at(range.start, Bias::Right), end: buffer.anchor_at(range.end, Bias::Left), }, ), _ => false, - }; - result + } } } @@ -1023,9 +1022,11 @@ impl AssistantContext { summary: new_summary, .. } => { - if self.summary.timestamp().map_or(true, |current_timestamp| { - new_summary.timestamp > current_timestamp - }) { + if self + .summary + .timestamp() + .is_none_or(|current_timestamp| new_summary.timestamp > current_timestamp) + { self.summary = ContextSummary::Content(new_summary); summary_generated = true; } @@ -1076,20 +1077,20 @@ impl AssistantContext { timestamp, .. } => { - if let Some(slash_command) = self.invoked_slash_commands.get_mut(&id) { - if timestamp > slash_command.timestamp { - slash_command.timestamp = timestamp; - match error_message { - Some(message) => { - slash_command.status = - InvokedSlashCommandStatus::Error(message.into()); - } - None => { - slash_command.status = InvokedSlashCommandStatus::Finished; - } + if let Some(slash_command) = self.invoked_slash_commands.get_mut(&id) + && timestamp > slash_command.timestamp + { + slash_command.timestamp = timestamp; + match error_message { + Some(message) => { + slash_command.status = + InvokedSlashCommandStatus::Error(message.into()); + } + None => { + slash_command.status = InvokedSlashCommandStatus::Finished; } - cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id }); } + cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id }); } } ContextOperation::BufferOperation(_) => unreachable!(), @@ -1339,7 +1340,7 @@ impl AssistantContext { let is_invalid = self .messages_metadata .get(&message_id) - .map_or(true, |metadata| { + .is_none_or(|metadata| { !metadata.is_cache_valid(&buffer, &message.offset_range) || *encountered_invalid }); @@ -1368,10 +1369,10 @@ impl AssistantContext { continue; } - if let Some(last_anchor) = last_anchor { - if message.id == last_anchor { - hit_last_anchor = true; - } + if let Some(last_anchor) = last_anchor + && message.id == last_anchor + { + hit_last_anchor = true; } new_anchor_needs_caching = new_anchor_needs_caching @@ -1406,14 +1407,14 @@ impl AssistantContext { if !self.pending_completions.is_empty() { return; } - if let Some(cache_configuration) = cache_configuration { - if !cache_configuration.should_speculate { - return; - } + if let Some(cache_configuration) = cache_configuration + && !cache_configuration.should_speculate + { + return; } let request = { - let mut req = self.to_completion_request(Some(&model), cx); + let mut req = self.to_completion_request(Some(model), cx); // Skip the last message because it's likely to change and // therefore would be a waste to cache. req.messages.pop(); @@ -1428,7 +1429,7 @@ impl AssistantContext { let model = Arc::clone(model); self.pending_cache_warming_task = cx.spawn(async move |this, cx| { async move { - match model.stream_completion(request, &cx).await { + match model.stream_completion(request, cx).await { Ok(mut stream) => { stream.next().await; log::info!("Cache warming completed successfully"); @@ -1552,25 +1553,24 @@ impl AssistantContext { }) .map(ToOwned::to_owned) .collect::>(); - if let Some(command) = self.slash_commands.command(name, cx) { - if !command.requires_argument() || !arguments.is_empty() { - let start_ix = offset + command_line.name.start - 1; - let end_ix = offset - + command_line - .arguments - .last() - .map_or(command_line.name.end, |argument| argument.end); - let source_range = - buffer.anchor_after(start_ix)..buffer.anchor_after(end_ix); - let pending_command = ParsedSlashCommand { - name: name.to_string(), - arguments, - source_range, - status: PendingSlashCommandStatus::Idle, - }; - updated.push(pending_command.clone()); - new_commands.push(pending_command); - } + if let Some(command) = self.slash_commands.command(name, cx) + && (!command.requires_argument() || !arguments.is_empty()) + { + let start_ix = offset + command_line.name.start - 1; + let end_ix = offset + + command_line + .arguments + .last() + .map_or(command_line.name.end, |argument| argument.end); + let source_range = buffer.anchor_after(start_ix)..buffer.anchor_after(end_ix); + let pending_command = ParsedSlashCommand { + name: name.to_string(), + arguments, + source_range, + status: PendingSlashCommandStatus::Idle, + }; + updated.push(pending_command.clone()); + new_commands.push(pending_command); } } @@ -1661,12 +1661,12 @@ impl AssistantContext { ) -> Range { let buffer = self.buffer.read(cx); let start_ix = match all_annotations - .binary_search_by(|probe| probe.range().end.cmp(&range.start, &buffer)) + .binary_search_by(|probe| probe.range().end.cmp(&range.start, buffer)) { Ok(ix) | Err(ix) => ix, }; let end_ix = match all_annotations - .binary_search_by(|probe| probe.range().start.cmp(&range.end, &buffer)) + .binary_search_by(|probe| probe.range().start.cmp(&range.end, buffer)) { Ok(ix) => ix + 1, Err(ix) => ix, @@ -1799,14 +1799,13 @@ impl AssistantContext { }); let end = this.buffer.read(cx).anchor_before(insert_position); - if run_commands_in_text { - if let Some(invoked_slash_command) = + if run_commands_in_text + && let Some(invoked_slash_command) = this.invoked_slash_commands.get_mut(&command_id) - { - invoked_slash_command - .run_commands_in_ranges - .push(start..end); - } + { + invoked_slash_command + .run_commands_in_ranges + .push(start..end); } } SlashCommandEvent::EndSection => { @@ -1862,7 +1861,7 @@ impl AssistantContext { { let newline_offset = insert_position.saturating_sub(1); if buffer.contains_str_at(newline_offset, "\n") - && last_section_range.map_or(true, |last_section_range| { + && last_section_range.is_none_or(|last_section_range| { !last_section_range .to_offset(buffer) .contains(&newline_offset) @@ -2045,7 +2044,7 @@ impl AssistantContext { let task = cx.spawn({ async move |this, cx| { - let stream = model.stream_completion(request, &cx); + let stream = model.stream_completion(request, cx); let assistant_message_id = assistant_message.id; let mut response_latency = None; let stream_completion = async { @@ -2081,15 +2080,12 @@ impl AssistantContext { match event { LanguageModelCompletionEvent::StatusUpdate(status_update) => { - match status_update { - CompletionRequestStatus::UsageUpdated { amount, limit } => { - this.update_model_request_usage( - amount as u32, - limit, - cx, - ); - } - _ => {} + if let CompletionRequestStatus::UsageUpdated { amount, limit } = status_update { + this.update_model_request_usage( + amount as u32, + limit, + cx, + ); } } LanguageModelCompletionEvent::StartMessage { .. } => {} @@ -2315,10 +2311,7 @@ impl AssistantContext { let mut request_message = LanguageModelRequestMessage { role: message.role, content: Vec::new(), - cache: message - .cache - .as_ref() - .map_or(false, |cache| cache.is_anchor), + cache: message.cache.as_ref().is_some_and(|cache| cache.is_anchor), }; while let Some(content) = contents.peek() { @@ -2708,7 +2701,7 @@ impl AssistantContext { self.summary_task = cx.spawn(async move |this, cx| { let result = async { - let stream = model.model.stream_completion_text(request, &cx); + let stream = model.model.stream_completion_text(request, cx); let mut messages = stream.await?; let mut replaced = !replace_old; @@ -2741,10 +2734,10 @@ impl AssistantContext { } this.read_with(cx, |this, _cx| { - if let Some(summary) = this.summary.content() { - if summary.text.is_empty() { - bail!("Model generated an empty summary"); - } + if let Some(summary) = this.summary.content() + && summary.text.is_empty() + { + bail!("Model generated an empty summary"); } Ok(()) })??; @@ -2799,7 +2792,7 @@ impl AssistantContext { let mut current_message = messages.next(); while let Some(offset) = offsets.next() { // Locate the message that contains the offset. - while current_message.as_ref().map_or(false, |message| { + while current_message.as_ref().is_some_and(|message| { !message.offset_range.contains(&offset) && messages.peek().is_some() }) { current_message = messages.next(); @@ -2809,7 +2802,7 @@ impl AssistantContext { }; // Skip offsets that are in the same message. - while offsets.peek().map_or(false, |offset| { + while offsets.peek().is_some_and(|offset| { message.offset_range.contains(offset) || messages.peek().is_none() }) { offsets.next(); @@ -2924,18 +2917,18 @@ impl AssistantContext { fs.create_dir(contexts_dir().as_ref()).await?; // rename before write ensures that only one file exists - if let Some(old_path) = old_path.as_ref() { - if new_path.as_path() != old_path.as_ref() { - fs.rename( - &old_path, - &new_path, - RenameOptions { - overwrite: true, - ignore_if_exists: true, - }, - ) - .await?; - } + if let Some(old_path) = old_path.as_ref() + && new_path.as_path() != old_path.as_ref() + { + fs.rename( + old_path, + &new_path, + RenameOptions { + overwrite: true, + ignore_if_exists: true, + }, + ) + .await?; } // update path before write in case it fails diff --git a/crates/assistant_context/src/assistant_context_tests.rs b/crates/assistant_context/src/assistant_context_tests.rs index efcad8ed96..28cc8ef8f0 100644 --- a/crates/assistant_context/src/assistant_context_tests.rs +++ b/crates/assistant_context/src/assistant_context_tests.rs @@ -1055,7 +1055,7 @@ fn test_mark_cache_anchors(cx: &mut App) { assert_eq!( messages_cache(&context, cx) .iter() - .filter(|(_, cache)| cache.as_ref().map_or(false, |cache| cache.is_anchor)) + .filter(|(_, cache)| cache.as_ref().is_some_and(|cache| cache.is_anchor)) .count(), 0, "Empty messages should not have any cache anchors." @@ -1083,7 +1083,7 @@ fn test_mark_cache_anchors(cx: &mut App) { assert_eq!( messages_cache(&context, cx) .iter() - .filter(|(_, cache)| cache.as_ref().map_or(false, |cache| cache.is_anchor)) + .filter(|(_, cache)| cache.as_ref().is_some_and(|cache| cache.is_anchor)) .count(), 0, "Messages should not be marked for cache before going over the token minimum." @@ -1098,7 +1098,7 @@ fn test_mark_cache_anchors(cx: &mut App) { assert_eq!( messages_cache(&context, cx) .iter() - .map(|(_, cache)| cache.as_ref().map_or(false, |cache| cache.is_anchor)) + .map(|(_, cache)| cache.as_ref().is_some_and(|cache| cache.is_anchor)) .collect::>(), vec![true, true, false], "Last message should not be an anchor on speculative request." @@ -1116,7 +1116,7 @@ fn test_mark_cache_anchors(cx: &mut App) { assert_eq!( messages_cache(&context, cx) .iter() - .map(|(_, cache)| cache.as_ref().map_or(false, |cache| cache.is_anchor)) + .map(|(_, cache)| cache.as_ref().is_some_and(|cache| cache.is_anchor)) .collect::>(), vec![false, true, true, false], "Most recent message should also be cached if not a speculative request." @@ -1300,7 +1300,7 @@ fn test_summarize_error( context.assist(cx); }); - simulate_successful_response(&model, cx); + simulate_successful_response(model, cx); context.read_with(cx, |context, _| { assert!(!context.summary().content().unwrap().done); diff --git a/crates/assistant_context/src/context_store.rs b/crates/assistant_context/src/context_store.rs index 622d8867a7..6d13531a57 100644 --- a/crates/assistant_context/src/context_store.rs +++ b/crates/assistant_context/src/context_store.rs @@ -320,7 +320,7 @@ impl ContextStore { .client .subscribe_to_entity(remote_id) .log_err() - .map(|subscription| subscription.set_entity(&cx.entity(), &mut cx.to_async())); + .map(|subscription| subscription.set_entity(&cx.entity(), &cx.to_async())); self.advertise_contexts(cx); } else { self.client_subscription = None; @@ -789,7 +789,7 @@ impl ContextStore { let fs = self.fs.clone(); cx.spawn(async move |this, cx| { pub static ZED_STATELESS: LazyLock = - LazyLock::new(|| std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty())); + LazyLock::new(|| std::env::var("ZED_STATELESS").is_ok_and(|v| !v.is_empty())); if *ZED_STATELESS { return Ok(()); } @@ -894,34 +894,33 @@ impl ContextStore { return; }; - if protocol.capable(context_server::protocol::ServerCapability::Prompts) { - if let Some(response) = protocol + if protocol.capable(context_server::protocol::ServerCapability::Prompts) + && let Some(response) = protocol .request::(()) .await .log_err() - { - let slash_command_ids = response - .prompts - .into_iter() - .filter(assistant_slash_commands::acceptable_prompt) - .map(|prompt| { - log::info!("registering context server command: {:?}", prompt.name); - slash_command_working_set.insert(Arc::new( - assistant_slash_commands::ContextServerSlashCommand::new( - context_server_store.clone(), - server.id(), - prompt, - ), - )) - }) - .collect::>(); - - this.update(cx, |this, _cx| { - this.context_server_slash_command_ids - .insert(server_id.clone(), slash_command_ids); + { + let slash_command_ids = response + .prompts + .into_iter() + .filter(assistant_slash_commands::acceptable_prompt) + .map(|prompt| { + log::info!("registering context server command: {:?}", prompt.name); + slash_command_working_set.insert(Arc::new( + assistant_slash_commands::ContextServerSlashCommand::new( + context_server_store.clone(), + server.id(), + prompt, + ), + )) }) - .log_err(); - } + .collect::>(); + + this.update(cx, |this, _cx| { + this.context_server_slash_command_ids + .insert(server_id.clone(), slash_command_ids); + }) + .log_err(); } }) .detach(); diff --git a/crates/assistant_slash_commands/src/context_server_command.rs b/crates/assistant_slash_commands/src/context_server_command.rs index f223d3b184..6caa1beb3b 100644 --- a/crates/assistant_slash_commands/src/context_server_command.rs +++ b/crates/assistant_slash_commands/src/context_server_command.rs @@ -39,12 +39,12 @@ impl SlashCommand for ContextServerSlashCommand { fn label(&self, cx: &App) -> language::CodeLabel { let mut parts = vec![self.prompt.name.as_str()]; - if let Some(args) = &self.prompt.arguments { - if let Some(arg) = args.first() { - parts.push(arg.name.as_str()); - } + if let Some(args) = &self.prompt.arguments + && let Some(arg) = args.first() + { + parts.push(arg.name.as_str()); } - create_label_for_command(&parts[0], &parts[1..], cx) + create_label_for_command(parts[0], &parts[1..], cx) } fn description(&self) -> String { @@ -62,9 +62,10 @@ impl SlashCommand for ContextServerSlashCommand { } fn requires_argument(&self) -> bool { - self.prompt.arguments.as_ref().map_or(false, |args| { - args.iter().any(|arg| arg.required == Some(true)) - }) + self.prompt + .arguments + .as_ref() + .is_some_and(|args| args.iter().any(|arg| arg.required == Some(true))) } fn complete_argument( diff --git a/crates/assistant_slash_commands/src/delta_command.rs b/crates/assistant_slash_commands/src/delta_command.rs index 8c840c17b2..2cc4591386 100644 --- a/crates/assistant_slash_commands/src/delta_command.rs +++ b/crates/assistant_slash_commands/src/delta_command.rs @@ -66,23 +66,22 @@ impl SlashCommand for DeltaSlashCommand { .metadata .as_ref() .and_then(|value| serde_json::from_value::(value.clone()).ok()) + && paths.insert(metadata.path.clone()) { - if paths.insert(metadata.path.clone()) { - file_command_old_outputs.push( - context_buffer - .as_rope() - .slice(section.range.to_offset(&context_buffer)), - ); - file_command_new_outputs.push(Arc::new(FileSlashCommand).run( - std::slice::from_ref(&metadata.path), - context_slash_command_output_sections, - context_buffer.clone(), - workspace.clone(), - delegate.clone(), - window, - cx, - )); - } + file_command_old_outputs.push( + context_buffer + .as_rope() + .slice(section.range.to_offset(&context_buffer)), + ); + file_command_new_outputs.push(Arc::new(FileSlashCommand).run( + std::slice::from_ref(&metadata.path), + context_slash_command_output_sections, + context_buffer.clone(), + workspace.clone(), + delegate.clone(), + window, + cx, + )); } } @@ -95,25 +94,25 @@ impl SlashCommand for DeltaSlashCommand { .into_iter() .zip(file_command_new_outputs) { - if let Ok(new_output) = new_output { - if let Ok(new_output) = SlashCommandOutput::from_event_stream(new_output).await - { - if let Some(file_command_range) = new_output.sections.first() { - let new_text = &new_output.text[file_command_range.range.clone()]; - if old_text.chars().ne(new_text.chars()) { - changes_detected = true; - output.sections.extend(new_output.sections.into_iter().map( - |section| SlashCommandOutputSection { - range: output.text.len() + section.range.start - ..output.text.len() + section.range.end, - icon: section.icon, - label: section.label, - metadata: section.metadata, - }, - )); - output.text.push_str(&new_output.text); - } - } + if let Ok(new_output) = new_output + && let Ok(new_output) = SlashCommandOutput::from_event_stream(new_output).await + && let Some(file_command_range) = new_output.sections.first() + { + let new_text = &new_output.text[file_command_range.range.clone()]; + if old_text.chars().ne(new_text.chars()) { + changes_detected = true; + output + .sections + .extend(new_output.sections.into_iter().map(|section| { + SlashCommandOutputSection { + range: output.text.len() + section.range.start + ..output.text.len() + section.range.end, + icon: section.icon, + label: section.label, + metadata: section.metadata, + } + })); + output.text.push_str(&new_output.text); } } } diff --git a/crates/assistant_slash_commands/src/diagnostics_command.rs b/crates/assistant_slash_commands/src/diagnostics_command.rs index 2feabd8b1e..536fe9f0ef 100644 --- a/crates/assistant_slash_commands/src/diagnostics_command.rs +++ b/crates/assistant_slash_commands/src/diagnostics_command.rs @@ -61,7 +61,7 @@ impl DiagnosticsSlashCommand { snapshot: worktree.snapshot(), include_ignored: worktree .root_entry() - .map_or(false, |entry| entry.is_ignored), + .is_some_and(|entry| entry.is_ignored), include_root_name: true, candidates: project::Candidates::Entries, } @@ -249,7 +249,7 @@ fn collect_diagnostics( let worktree = worktree.read(cx); let worktree_root_path = Path::new(worktree.root_name()); let relative_path = path.strip_prefix(worktree_root_path).ok()?; - worktree.absolutize(&relative_path).ok() + worktree.absolutize(relative_path).ok() }) }) .is_some() @@ -280,10 +280,10 @@ fn collect_diagnostics( let mut project_summary = DiagnosticSummary::default(); for (project_path, path, summary) in diagnostic_summaries { - if let Some(path_matcher) = &options.path_matcher { - if !path_matcher.is_match(&path) { - continue; - } + if let Some(path_matcher) = &options.path_matcher + && !path_matcher.is_match(&path) + { + continue; } project_summary.error_count += summary.error_count; @@ -365,7 +365,7 @@ pub fn collect_buffer_diagnostics( ) { for (_, group) in snapshot.diagnostic_groups(None) { let entry = &group.entries[group.primary_ix]; - collect_diagnostic(output, entry, &snapshot, include_warnings) + collect_diagnostic(output, entry, snapshot, include_warnings) } } @@ -396,7 +396,7 @@ fn collect_diagnostic( let start_row = range.start.row.saturating_sub(EXCERPT_EXPANSION_SIZE); let end_row = (range.end.row + EXCERPT_EXPANSION_SIZE).min(snapshot.max_point().row) + 1; let excerpt_range = - Point::new(start_row, 0).to_offset(&snapshot)..Point::new(end_row, 0).to_offset(&snapshot); + Point::new(start_row, 0).to_offset(snapshot)..Point::new(end_row, 0).to_offset(snapshot); output.text.push_str("```"); if let Some(language_name) = snapshot.language().map(|l| l.code_fence_block_name()) { diff --git a/crates/assistant_slash_commands/src/file_command.rs b/crates/assistant_slash_commands/src/file_command.rs index c913ccc0f1..894aa94a27 100644 --- a/crates/assistant_slash_commands/src/file_command.rs +++ b/crates/assistant_slash_commands/src/file_command.rs @@ -92,7 +92,7 @@ impl FileSlashCommand { snapshot: worktree.snapshot(), include_ignored: worktree .root_entry() - .map_or(false, |entry| entry.is_ignored), + .is_some_and(|entry| entry.is_ignored), include_root_name: true, candidates: project::Candidates::Entries, } @@ -223,7 +223,7 @@ fn collect_files( cx: &mut App, ) -> impl Stream> + use<> { let Ok(matchers) = glob_inputs - .into_iter() + .iter() .map(|glob_input| { custom_path_matcher::PathMatcher::new(&[glob_input.to_owned()]) .with_context(|| format!("invalid path {glob_input}")) @@ -379,7 +379,7 @@ fn collect_files( } } - while let Some(_) = directory_stack.pop() { + while directory_stack.pop().is_some() { events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?; } } @@ -491,7 +491,7 @@ mod custom_path_matcher { impl PathMatcher { pub fn new(globs: &[String]) -> Result { let globs = globs - .into_iter() + .iter() .map(|glob| Glob::new(&SanitizedPath::from(glob).to_glob_string())) .collect::, _>>()?; let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect(); @@ -536,7 +536,7 @@ mod custom_path_matcher { let path_str = path.to_string_lossy(); let separator = std::path::MAIN_SEPARATOR_STR; if path_str.ends_with(separator) { - return false; + false } else { self.glob.is_match(path_str.to_string() + separator) } diff --git a/crates/assistant_slash_commands/src/tab_command.rs b/crates/assistant_slash_commands/src/tab_command.rs index ca7601bc4c..e4ae391a9c 100644 --- a/crates/assistant_slash_commands/src/tab_command.rs +++ b/crates/assistant_slash_commands/src/tab_command.rs @@ -195,16 +195,14 @@ fn tab_items_for_queries( } for editor in workspace.items_of_type::(cx) { - if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() { - if let Some(timestamp) = + if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() + && let Some(timestamp) = timestamps_by_entity_id.get(&editor.entity_id()) - { - if visited_buffers.insert(buffer.read(cx).remote_id()) { - let snapshot = buffer.read(cx).snapshot(); - let full_path = snapshot.resolve_file_path(cx, true); - open_buffers.push((full_path, snapshot, *timestamp)); - } - } + && visited_buffers.insert(buffer.read(cx).remote_id()) + { + let snapshot = buffer.read(cx).snapshot(); + let full_path = snapshot.resolve_file_path(cx, true); + open_buffers.push((full_path, snapshot, *timestamp)); } } diff --git a/crates/assistant_tool/src/tool_schema.rs b/crates/assistant_tool/src/tool_schema.rs index 7b48f93ba6..192f7c8a2b 100644 --- a/crates/assistant_tool/src/tool_schema.rs +++ b/crates/assistant_tool/src/tool_schema.rs @@ -24,16 +24,16 @@ pub fn adapt_schema_to_format( fn preprocess_json_schema(json: &mut Value) -> Result<()> { // `additionalProperties` defaults to `false` unless explicitly specified. // This prevents models from hallucinating tool parameters. - if let Value::Object(obj) = json { - if matches!(obj.get("type"), Some(Value::String(s)) if s == "object") { - if !obj.contains_key("additionalProperties") { - obj.insert("additionalProperties".to_string(), Value::Bool(false)); - } + if let Value::Object(obj) = json + && matches!(obj.get("type"), Some(Value::String(s)) if s == "object") + { + if !obj.contains_key("additionalProperties") { + obj.insert("additionalProperties".to_string(), Value::Bool(false)); + } - // OpenAI API requires non-missing `properties` - if !obj.contains_key("properties") { - obj.insert("properties".to_string(), Value::Object(Default::default())); - } + // OpenAI API requires non-missing `properties` + if !obj.contains_key("properties") { + obj.insert("properties".to_string(), Value::Object(Default::default())); } } Ok(()) @@ -59,10 +59,10 @@ fn adapt_to_json_schema_subset(json: &mut Value) -> Result<()> { ("optional", |value| value.is_boolean()), ]; for (key, predicate) in KEYS_TO_REMOVE { - if let Some(value) = obj.get(key) { - if predicate(value) { - obj.remove(key); - } + if let Some(value) = obj.get(key) + && predicate(value) + { + obj.remove(key); } } @@ -77,12 +77,12 @@ fn adapt_to_json_schema_subset(json: &mut Value) -> Result<()> { } // Handle oneOf -> anyOf conversion - if let Some(subschemas) = obj.get_mut("oneOf") { - if subschemas.is_array() { - let subschemas_clone = subschemas.clone(); - obj.remove("oneOf"); - obj.insert("anyOf".to_string(), subschemas_clone); - } + if let Some(subschemas) = obj.get_mut("oneOf") + && subschemas.is_array() + { + let subschemas_clone = subschemas.clone(); + obj.remove("oneOf"); + obj.insert("anyOf".to_string(), subschemas_clone); } // Recursively process all nested objects and arrays diff --git a/crates/assistant_tool/src/tool_working_set.rs b/crates/assistant_tool/src/tool_working_set.rs index c0a358917b..61f57affc7 100644 --- a/crates/assistant_tool/src/tool_working_set.rs +++ b/crates/assistant_tool/src/tool_working_set.rs @@ -156,13 +156,13 @@ fn resolve_context_server_tool_name_conflicts( if duplicated_tool_names.is_empty() { return context_server_tools - .into_iter() + .iter() .map(|tool| (resolve_tool_name(tool).into(), tool.clone())) .collect(); } context_server_tools - .into_iter() + .iter() .filter_map(|tool| { let mut tool_name = resolve_tool_name(tool); if !duplicated_tool_names.contains(&tool_name) { diff --git a/crates/assistant_tools/src/assistant_tools.rs b/crates/assistant_tools/src/assistant_tools.rs index bf668e6918..ce3b639cb2 100644 --- a/crates/assistant_tools/src/assistant_tools.rs +++ b/crates/assistant_tools/src/assistant_tools.rs @@ -72,11 +72,10 @@ pub fn init(http_client: Arc, cx: &mut App) { register_web_search_tool(&LanguageModelRegistry::global(cx), cx); cx.subscribe( &LanguageModelRegistry::global(cx), - move |registry, event, cx| match event { - language_model::Event::DefaultModelChanged => { + move |registry, event, cx| { + if let language_model::Event::DefaultModelChanged = event { register_web_search_tool(®istry, cx); } - _ => {} }, ) .detach(); @@ -86,7 +85,7 @@ fn register_web_search_tool(registry: &Entity, cx: &mut A let using_zed_provider = registry .read(cx) .default_model() - .map_or(false, |default| default.is_provided_by_zed()); + .is_some_and(|default| default.is_provided_by_zed()); if using_zed_provider { ToolRegistry::global(cx).register_tool(WebSearchTool); } else { diff --git a/crates/assistant_tools/src/edit_agent.rs b/crates/assistant_tools/src/edit_agent.rs index aa321aa8f3..665ece2baa 100644 --- a/crates/assistant_tools/src/edit_agent.rs +++ b/crates/assistant_tools/src/edit_agent.rs @@ -672,29 +672,30 @@ impl EditAgent { cx: &mut AsyncApp, ) -> Result>> { let mut messages_iter = conversation.messages.iter_mut(); - if let Some(last_message) = messages_iter.next_back() { - if last_message.role == Role::Assistant { - let old_content_len = last_message.content.len(); - last_message - .content - .retain(|content| !matches!(content, MessageContent::ToolUse(_))); - let new_content_len = last_message.content.len(); + if let Some(last_message) = messages_iter.next_back() + && last_message.role == Role::Assistant + { + let old_content_len = last_message.content.len(); + last_message + .content + .retain(|content| !matches!(content, MessageContent::ToolUse(_))); + let new_content_len = last_message.content.len(); - // We just removed pending tool uses from the content of the - // last message, so it doesn't make sense to cache it anymore - // (e.g., the message will look very different on the next - // request). Thus, we move the flag to the message prior to it, - // as it will still be a valid prefix of the conversation. - if old_content_len != new_content_len && last_message.cache { - if let Some(prev_message) = messages_iter.next_back() { - last_message.cache = false; - prev_message.cache = true; - } - } + // We just removed pending tool uses from the content of the + // last message, so it doesn't make sense to cache it anymore + // (e.g., the message will look very different on the next + // request). Thus, we move the flag to the message prior to it, + // as it will still be a valid prefix of the conversation. + if old_content_len != new_content_len + && last_message.cache + && let Some(prev_message) = messages_iter.next_back() + { + last_message.cache = false; + prev_message.cache = true; + } - if last_message.content.is_empty() { - conversation.messages.pop(); - } + if last_message.content.is_empty() { + conversation.messages.pop(); } } diff --git a/crates/assistant_tools/src/edit_agent/evals.rs b/crates/assistant_tools/src/edit_agent/evals.rs index 9a8e762455..ea2fa02663 100644 --- a/crates/assistant_tools/src/edit_agent/evals.rs +++ b/crates/assistant_tools/src/edit_agent/evals.rs @@ -1283,14 +1283,14 @@ impl EvalAssertion { // Parse the score from the response let re = regex::Regex::new(r"(\d+)").unwrap(); - if let Some(captures) = re.captures(&output) { - if let Some(score_match) = captures.get(1) { - let score = score_match.as_str().parse().unwrap_or(0); - return Ok(EvalAssertionOutcome { - score, - message: Some(output), - }); - } + if let Some(captures) = re.captures(&output) + && let Some(score_match) = captures.get(1) + { + let score = score_match.as_str().parse().unwrap_or(0); + return Ok(EvalAssertionOutcome { + score, + message: Some(output), + }); } anyhow::bail!("No score found in response. Raw output: {output}"); @@ -1586,7 +1586,7 @@ impl EditAgentTest { let has_system_prompt = eval .conversation .first() - .map_or(false, |msg| msg.role == Role::System); + .is_some_and(|msg| msg.role == Role::System); let messages = if has_system_prompt { eval.conversation } else { diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index e819c51e1e..33d08b4f88 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -155,10 +155,10 @@ impl Tool for EditFileTool { // It's also possible that the global config dir is configured to be inside the project, // so check for that edge case too. - if let Ok(canonical_path) = std::fs::canonicalize(&input.path) { - if canonical_path.starts_with(paths::config_dir()) { - return true; - } + if let Ok(canonical_path) = std::fs::canonicalize(&input.path) + && canonical_path.starts_with(paths::config_dir()) + { + return true; } // Check if path is inside the global config directory @@ -199,10 +199,10 @@ impl Tool for EditFileTool { .any(|c| c.as_os_str() == local_settings_folder.as_os_str()) { description.push_str(" (local settings)"); - } else if let Ok(canonical_path) = std::fs::canonicalize(&input.path) { - if canonical_path.starts_with(paths::config_dir()) { - description.push_str(" (global settings)"); - } + } else if let Ok(canonical_path) = std::fs::canonicalize(&input.path) + && canonical_path.starts_with(paths::config_dir()) + { + description.push_str(" (global settings)"); } description @@ -536,7 +536,7 @@ fn resolve_path( let parent_entry = parent_project_path .as_ref() - .and_then(|path| project.entry_for_path(&path, cx)) + .and_then(|path| project.entry_for_path(path, cx)) .context("Can't create file: parent directory doesn't exist")?; anyhow::ensure!( @@ -723,13 +723,13 @@ impl EditFileToolCard { let buffer = buffer.read(cx); let diff = diff.read(cx); let mut ranges = diff - .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, cx) - .map(|diff_hunk| diff_hunk.buffer_range.to_point(&buffer)) + .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx) + .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer)) .collect::>(); ranges.extend( self.revealed_ranges .iter() - .map(|range| range.to_point(&buffer)), + .map(|range| range.to_point(buffer)), ); ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end))); @@ -1356,8 +1356,7 @@ mod tests { mode: mode.clone(), }; - let result = cx.update(|cx| resolve_path(&input, project, cx)); - result + cx.update(|cx| resolve_path(&input, project, cx)) } fn assert_resolved_path_eq(path: anyhow::Result, expected: &str) { diff --git a/crates/assistant_tools/src/grep_tool.rs b/crates/assistant_tools/src/grep_tool.rs index a5ce07823f..1dd74b99e7 100644 --- a/crates/assistant_tools/src/grep_tool.rs +++ b/crates/assistant_tools/src/grep_tool.rs @@ -188,15 +188,14 @@ impl Tool for GrepTool { // Check if this file should be excluded based on its worktree settings if let Ok(Some(project_path)) = project.read_with(cx, |project, cx| { project.find_project_path(&path, cx) - }) { - if cx.update(|cx| { + }) + && cx.update(|cx| { let worktree_settings = WorktreeSettings::get(Some((&project_path).into()), cx); worktree_settings.is_path_excluded(&project_path.path) || worktree_settings.is_path_private(&project_path.path) }).unwrap_or(false) { continue; } - } while *parse_status.borrow() != ParseStatus::Idle { parse_status.changed().await?; @@ -284,12 +283,11 @@ impl Tool for GrepTool { output.extend(snapshot.text_for_range(range)); output.push_str("\n```\n"); - if let Some(ancestor_range) = ancestor_range { - if end_row < ancestor_range.end.row { + if let Some(ancestor_range) = ancestor_range + && end_row < ancestor_range.end.row { let remaining_lines = ancestor_range.end.row - end_row; writeln!(output, "\n{} lines remaining in ancestor node. Read the file to see all.", remaining_lines)?; } - } matches_found += 1; } @@ -894,7 +892,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.is_empty(), "grep_tool should not find files outside the project worktree" @@ -920,7 +918,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.iter().any(|p| p.contains("allowed_file.rs")), "grep_tool should be able to search files inside worktrees" @@ -946,7 +944,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.is_empty(), "grep_tool should not search files in .secretdir (file_scan_exclusions)" @@ -971,7 +969,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.is_empty(), "grep_tool should not search .mymetadata files (file_scan_exclusions)" @@ -997,7 +995,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.is_empty(), "grep_tool should not search .mysecrets (private_files)" @@ -1022,7 +1020,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.is_empty(), "grep_tool should not search .privatekey files (private_files)" @@ -1047,7 +1045,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.is_empty(), "grep_tool should not search .mysensitive files (private_files)" @@ -1073,7 +1071,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.iter().any(|p| p.contains("normal_file.rs")), "Should be able to search normal files" @@ -1100,7 +1098,7 @@ mod tests { }) .await; let results = result.unwrap(); - let paths = extract_paths_from_results(&results.content.as_str().unwrap()); + let paths = extract_paths_from_results(results.content.as_str().unwrap()); assert!( paths.is_empty(), "grep_tool should not allow escaping project boundaries with relative paths" @@ -1206,7 +1204,7 @@ mod tests { .unwrap(); let content = result.content.as_str().unwrap(); - let paths = extract_paths_from_results(&content); + let paths = extract_paths_from_results(content); // Should find matches in non-private files assert!( @@ -1271,7 +1269,7 @@ mod tests { .unwrap(); let content = result.content.as_str().unwrap(); - let paths = extract_paths_from_results(&content); + let paths = extract_paths_from_results(content); // Should only find matches in worktree1 *.rs files (excluding private ones) assert!( diff --git a/crates/assistant_tools/src/project_notifications_tool.rs b/crates/assistant_tools/src/project_notifications_tool.rs index c65cfd0ca7..e30d80207d 100644 --- a/crates/assistant_tools/src/project_notifications_tool.rs +++ b/crates/assistant_tools/src/project_notifications_tool.rs @@ -81,7 +81,7 @@ fn fit_patch_to_size(patch: &str, max_size: usize) -> String { // Compression level 1: remove context lines in diff bodies, but // leave the counts and positions of inserted/deleted lines let mut current_size = patch.len(); - let mut file_patches = split_patch(&patch); + let mut file_patches = split_patch(patch); file_patches.sort_by_key(|patch| patch.len()); let compressed_patches = file_patches .iter() diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 68b870e40f..766ee3b161 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -201,7 +201,7 @@ impl Tool for ReadFileTool { buffer .file() .as_ref() - .map_or(true, |file| !file.disk_state().exists()) + .is_none_or(|file| !file.disk_state().exists()) })? { anyhow::bail!("{file_path} not found"); } diff --git a/crates/assistant_tools/src/schema.rs b/crates/assistant_tools/src/schema.rs index 10a8bf0acd..dab7384efd 100644 --- a/crates/assistant_tools/src/schema.rs +++ b/crates/assistant_tools/src/schema.rs @@ -43,12 +43,11 @@ impl Transform for ToJsonSchemaSubsetTransform { fn transform(&mut self, schema: &mut Schema) { // Ensure that the type field is not an array, this happens when we use // Option, the type will be [T, "null"]. - if let Some(type_field) = schema.get_mut("type") { - if let Some(types) = type_field.as_array() { - if let Some(first_type) = types.first() { - *type_field = first_type.clone(); - } - } + if let Some(type_field) = schema.get_mut("type") + && let Some(types) = type_field.as_array() + && let Some(first_type) = types.first() + { + *type_field = first_type.clone(); } // oneOf is not supported, use anyOf instead diff --git a/crates/assistant_tools/src/terminal_tool.rs b/crates/assistant_tools/src/terminal_tool.rs index 46227f130d..358d62ee1a 100644 --- a/crates/assistant_tools/src/terminal_tool.rs +++ b/crates/assistant_tools/src/terminal_tool.rs @@ -59,12 +59,9 @@ impl TerminalTool { } if which::which("bash").is_ok() { - log::info!("agent selected bash for terminal tool"); "bash".into() } else { - let shell = get_system_shell(); - log::info!("agent selected {shell} for terminal tool"); - shell + get_system_shell() } }); Self { @@ -105,7 +102,7 @@ impl Tool for TerminalTool { let first_line = lines.next().unwrap_or_default(); let remaining_line_count = lines.count(); match remaining_line_count { - 0 => MarkdownInlineCode(&first_line).to_string(), + 0 => MarkdownInlineCode(first_line).to_string(), 1 => MarkdownInlineCode(&format!( "{} - {} more line", first_line, remaining_line_count @@ -216,7 +213,8 @@ impl Tool for TerminalTool { async move |cx| { let program = program.await; let env = env.await; - let terminal = project + + project .update(cx, |project, cx| { project.create_terminal( TerminalKind::Task(task::SpawnInTerminal { @@ -229,8 +227,7 @@ impl Tool for TerminalTool { cx, ) })? - .await; - terminal + .await } }); @@ -387,7 +384,7 @@ fn working_dir( let project = project.read(cx); let cd = &input.cd; - if cd == "." || cd == "" { + if cd == "." || cd.is_empty() { // Accept "." or "" as meaning "the one worktree" if we only have one worktree. let mut worktrees = project.worktrees(cx); @@ -412,10 +409,8 @@ fn working_dir( { return Ok(Some(input_path.into())); } - } else { - if let Some(worktree) = project.worktree_for_root_name(cd, cx) { - return Ok(Some(worktree.read(cx).abs_path().to_path_buf())); - } + } else if let Some(worktree) = project.worktree_for_root_name(cd, cx) { + return Ok(Some(worktree.read(cx).abs_path().to_path_buf())); } anyhow::bail!("`cd` directory {cd:?} was not in any of the project's worktrees."); diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 4d0d2d5984..2150873cad 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -543,7 +543,7 @@ impl AutoUpdater { async fn update(this: Entity, mut cx: AsyncApp) -> Result<()> { let (client, installed_version, previous_status, release_channel) = - this.read_with(&mut cx, |this, cx| { + this.read_with(&cx, |this, cx| { ( this.http_client.clone(), this.current_version, diff --git a/crates/auto_update_helper/src/dialog.rs b/crates/auto_update_helper/src/dialog.rs index 757819df51..903ac34da2 100644 --- a/crates/auto_update_helper/src/dialog.rs +++ b/crates/auto_update_helper/src/dialog.rs @@ -186,11 +186,11 @@ unsafe extern "system" fn wnd_proc( }), WM_TERMINATE => { with_dialog_data(hwnd, |data| { - if let Ok(result) = data.borrow_mut().rx.recv() { - if let Err(e) = result { - log::error!("Failed to update Zed: {:?}", e); - show_error(format!("Error: {:?}", e)); - } + if let Ok(result) = data.borrow_mut().rx.recv() + && let Err(e) = result + { + log::error!("Failed to update Zed: {:?}", e); + show_error(format!("Error: {:?}", e)); } }); unsafe { PostQuitMessage(0) }; diff --git a/crates/bedrock/src/bedrock.rs b/crates/bedrock/src/bedrock.rs index 1c6a9bd0a1..c8315d4201 100644 --- a/crates/bedrock/src/bedrock.rs +++ b/crates/bedrock/src/bedrock.rs @@ -54,11 +54,7 @@ pub async fn stream_completion( )]))); } - if request - .tools - .as_ref() - .map_or(false, |t| !t.tools.is_empty()) - { + if request.tools.as_ref().is_some_and(|t| !t.tools.is_empty()) { response = response.set_tool_config(request.tools); } diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index 8eed7497da..a6b27476fe 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -82,11 +82,12 @@ impl Render for Breadcrumbs { } text_style.color = Color::Muted.color(cx); - if index == 0 && !TabBarSettings::get_global(cx).show && active_item.is_dirty(cx) { - if let Some(styled_element) = apply_dirty_filename_style(&segment, &text_style, cx) - { - return styled_element; - } + if index == 0 + && !TabBarSettings::get_global(cx).show + && active_item.is_dirty(cx) + && let Some(styled_element) = apply_dirty_filename_style(&segment, &text_style, cx) + { + return styled_element; } StyledText::new(segment.text.replace('\n', "⏎")) @@ -231,7 +232,7 @@ fn apply_dirty_filename_style( let highlight = vec![(filename_position..text.len(), highlight_style)]; Some( StyledText::new(text) - .with_default_highlights(&text_style, highlight) + .with_default_highlights(text_style, highlight) .into_any(), ) } diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 97f529fe37..6b38fe5576 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -572,14 +572,14 @@ impl BufferDiffInner { pending_range.end.column = 0; } - if pending_range == (start_point..end_point) { - if !buffer.has_edits_since_in_range( + if pending_range == (start_point..end_point) + && !buffer.has_edits_since_in_range( &pending_hunk.buffer_version, start_anchor..end_anchor, - ) { - has_pending = true; - secondary_status = pending_hunk.new_status; - } + ) + { + has_pending = true; + secondary_status = pending_hunk.new_status; } } @@ -928,7 +928,7 @@ impl BufferDiff { let new_index_text = self.inner.stage_or_unstage_hunks_impl( &self.secondary_diff.as_ref()?.read(cx).inner, stage, - &hunks, + hunks, buffer, file_exists, ); @@ -952,12 +952,12 @@ impl BufferDiff { cx: &App, ) -> Option> { let start = self - .hunks_intersecting_range(range.clone(), &buffer, cx) + .hunks_intersecting_range(range.clone(), buffer, cx) .next()? .buffer_range .start; let end = self - .hunks_intersecting_range_rev(range.clone(), &buffer) + .hunks_intersecting_range_rev(range.clone(), buffer) .next()? .buffer_range .end; @@ -1031,21 +1031,20 @@ impl BufferDiff { && state.base_text.syntax_update_count() == new_state.base_text.syntax_update_count() => { - (false, new_state.compare(&state, buffer)) + (false, new_state.compare(state, buffer)) } _ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)), }; - if let Some(secondary_changed_range) = secondary_diff_change { - if let Some(secondary_hunk_range) = - self.range_to_hunk_range(secondary_changed_range, &buffer, cx) - { - if let Some(range) = &mut changed_range { - range.start = secondary_hunk_range.start.min(&range.start, &buffer); - range.end = secondary_hunk_range.end.max(&range.end, &buffer); - } else { - changed_range = Some(secondary_hunk_range); - } + if let Some(secondary_changed_range) = secondary_diff_change + && let Some(secondary_hunk_range) = + self.range_to_hunk_range(secondary_changed_range, buffer, cx) + { + if let Some(range) = &mut changed_range { + range.start = secondary_hunk_range.start.min(&range.start, buffer); + range.end = secondary_hunk_range.end.max(&range.end, buffer); + } else { + changed_range = Some(secondary_hunk_range); } } @@ -1057,8 +1056,8 @@ impl BufferDiff { if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last()) { if let Some(range) = &mut changed_range { - range.start = range.start.min(&first.buffer_range.start, &buffer); - range.end = range.end.max(&last.buffer_range.end, &buffer); + range.start = range.start.min(&first.buffer_range.start, buffer); + range.end = range.end.max(&last.buffer_range.end, buffer); } else { changed_range = Some(first.buffer_range.start..last.buffer_range.end); } @@ -1797,7 +1796,7 @@ mod tests { uncommitted_diff.update(cx, |diff, cx| { let hunks = diff - .hunks_intersecting_range(hunk_range.clone(), &buffer, &cx) + .hunks_intersecting_range(hunk_range.clone(), &buffer, cx) .collect::>(); for hunk in &hunks { assert_ne!( @@ -1812,7 +1811,7 @@ mod tests { .to_string(); let hunks = diff - .hunks_intersecting_range(hunk_range.clone(), &buffer, &cx) + .hunks_intersecting_range(hunk_range.clone(), &buffer, cx) .collect::>(); for hunk in &hunks { assert_eq!( @@ -1870,7 +1869,7 @@ mod tests { .to_string(); assert_eq!(new_index_text, buffer_text); - let hunk = diff.hunks(&buffer, &cx).next().unwrap(); + let hunk = diff.hunks(&buffer, cx).next().unwrap(); assert_eq!( hunk.secondary_status, DiffHunkSecondaryStatus::SecondaryHunkRemovalPending @@ -1882,7 +1881,7 @@ mod tests { .to_string(); assert_eq!(index_text, head_text); - let hunk = diff.hunks(&buffer, &cx).next().unwrap(); + let hunk = diff.hunks(&buffer, cx).next().unwrap(); // optimistically unstaged (fine, could also be HasSecondaryHunk) assert_eq!( hunk.secondary_status, diff --git a/crates/call/src/call_impl/mod.rs b/crates/call/src/call_impl/mod.rs index 71c3149324..156a80faba 100644 --- a/crates/call/src/call_impl/mod.rs +++ b/crates/call/src/call_impl/mod.rs @@ -116,7 +116,7 @@ impl ActiveCall { envelope: TypedEnvelope, mut cx: AsyncApp, ) -> Result { - let user_store = this.read_with(&mut cx, |this, _| this.user_store.clone())?; + let user_store = this.read_with(&cx, |this, _| this.user_store.clone())?; let call = IncomingCall { room_id: envelope.payload.room_id, participants: user_store @@ -147,7 +147,7 @@ impl ActiveCall { let mut incoming_call = this.incoming_call.0.borrow_mut(); if incoming_call .as_ref() - .map_or(false, |call| call.room_id == envelope.payload.room_id) + .is_some_and(|call| call.room_id == envelope.payload.room_id) { incoming_call.take(); } diff --git a/crates/call/src/call_impl/participant.rs b/crates/call/src/call_impl/participant.rs index 8e1e264a23..6fb6a2eb79 100644 --- a/crates/call/src/call_impl/participant.rs +++ b/crates/call/src/call_impl/participant.rs @@ -64,7 +64,7 @@ pub struct RemoteParticipant { impl RemoteParticipant { pub fn has_video_tracks(&self) -> bool { - return !self.video_tracks.is_empty(); + !self.video_tracks.is_empty() } pub fn can_write(&self) -> bool { diff --git a/crates/call/src/call_impl/room.rs b/crates/call/src/call_impl/room.rs index 73cb8518a6..ffe4c6c251 100644 --- a/crates/call/src/call_impl/room.rs +++ b/crates/call/src/call_impl/room.rs @@ -827,24 +827,23 @@ impl Room { ); Audio::play_sound(Sound::Joined, cx); - if let Some(livekit_participants) = &livekit_participants { - if let Some(livekit_participant) = livekit_participants + if let Some(livekit_participants) = &livekit_participants + && let Some(livekit_participant) = livekit_participants .get(&ParticipantIdentity(user.id.to_string())) + { + for publication in + livekit_participant.track_publications().into_values() { - for publication in - livekit_participant.track_publications().into_values() - { - if let Some(track) = publication.track() { - this.livekit_room_updated( - RoomEvent::TrackSubscribed { - track, - publication, - participant: livekit_participant.clone(), - }, - cx, - ) - .warn_on_err(); - } + if let Some(track) = publication.track() { + this.livekit_room_updated( + RoomEvent::TrackSubscribed { + track, + publication, + participant: livekit_participant.clone(), + }, + cx, + ) + .warn_on_err(); } } } @@ -940,10 +939,8 @@ impl Room { self.client.user_id() ) })?; - if self.live_kit.as_ref().map_or(true, |kit| kit.deafened) { - if publication.is_audio() { - publication.set_enabled(false, cx); - } + if self.live_kit.as_ref().is_none_or(|kit| kit.deafened) && publication.is_audio() { + publication.set_enabled(false, cx); } match track { livekit_client::RemoteTrack::Audio(track) => { @@ -1005,10 +1002,10 @@ impl Room { for (sid, participant) in &mut self.remote_participants { participant.speaking = speaker_ids.binary_search(sid).is_ok(); } - if let Some(id) = self.client.user_id() { - if let Some(room) = &mut self.live_kit { - room.speaking = speaker_ids.binary_search(&id).is_ok(); - } + if let Some(id) = self.client.user_id() + && let Some(room) = &mut self.live_kit + { + room.speaking = speaker_ids.binary_search(&id).is_ok(); } } @@ -1042,18 +1039,16 @@ impl Room { if let LocalTrack::Published { track_publication, .. } = &room.microphone_track + && track_publication.sid() == publication.sid() { - if track_publication.sid() == publication.sid() { - room.microphone_track = LocalTrack::None; - } + room.microphone_track = LocalTrack::None; } if let LocalTrack::Published { track_publication, .. } = &room.screen_track + && track_publication.sid() == publication.sid() { - if track_publication.sid() == publication.sid() { - room.screen_track = LocalTrack::None; - } + room.screen_track = LocalTrack::None; } } } @@ -1178,7 +1173,7 @@ impl Room { this.update(cx, |this, cx| { this.shared_projects.insert(project.downgrade()); let active_project = this.local_participant.active_project.as_ref(); - if active_project.map_or(false, |location| *location == project) { + if active_project.is_some_and(|location| *location == project) { this.set_location(Some(&project), cx) } else { Task::ready(Ok(())) @@ -1251,9 +1246,9 @@ impl Room { } pub fn is_sharing_screen(&self) -> bool { - self.live_kit.as_ref().map_or(false, |live_kit| { - !matches!(live_kit.screen_track, LocalTrack::None) - }) + self.live_kit + .as_ref() + .is_some_and(|live_kit| !matches!(live_kit.screen_track, LocalTrack::None)) } pub fn shared_screen_id(&self) -> Option { @@ -1266,13 +1261,13 @@ impl Room { } pub fn is_sharing_mic(&self) -> bool { - self.live_kit.as_ref().map_or(false, |live_kit| { - !matches!(live_kit.microphone_track, LocalTrack::None) - }) + self.live_kit + .as_ref() + .is_some_and(|live_kit| !matches!(live_kit.microphone_track, LocalTrack::None)) } pub fn is_muted(&self) -> bool { - self.live_kit.as_ref().map_or(false, |live_kit| { + self.live_kit.as_ref().is_some_and(|live_kit| { matches!(live_kit.microphone_track, LocalTrack::None) || live_kit.muted_by_user || live_kit.deafened @@ -1282,13 +1277,13 @@ impl Room { pub fn muted_by_user(&self) -> bool { self.live_kit .as_ref() - .map_or(false, |live_kit| live_kit.muted_by_user) + .is_some_and(|live_kit| live_kit.muted_by_user) } pub fn is_speaking(&self) -> bool { self.live_kit .as_ref() - .map_or(false, |live_kit| live_kit.speaking) + .is_some_and(|live_kit| live_kit.speaking) } pub fn is_deafened(&self) -> Option { @@ -1484,10 +1479,8 @@ impl Room { self.set_deafened(deafened, cx); - if should_change_mute { - if let Some(task) = self.set_mute(deafened, cx) { - task.detach_and_log_err(cx); - } + if should_change_mute && let Some(task) = self.set_mute(deafened, cx) { + task.detach_and_log_err(cx); } } } diff --git a/crates/channel/src/channel_buffer.rs b/crates/channel/src/channel_buffer.rs index 183f7eb3c6..943e819ad6 100644 --- a/crates/channel/src/channel_buffer.rs +++ b/crates/channel/src/channel_buffer.rs @@ -82,7 +82,7 @@ impl ChannelBuffer { collaborators: Default::default(), acknowledge_task: None, channel_id: channel.id, - subscription: Some(subscription.set_entity(&cx.entity(), &mut cx.to_async())), + subscription: Some(subscription.set_entity(&cx.entity(), &cx.to_async())), user_store, channel_store, }; @@ -110,7 +110,7 @@ impl ChannelBuffer { let Ok(subscription) = self.client.subscribe_to_entity(self.channel_id.0) else { return; }; - self.subscription = Some(subscription.set_entity(&cx.entity(), &mut cx.to_async())); + self.subscription = Some(subscription.set_entity(&cx.entity(), &cx.to_async())); cx.emit(ChannelBufferEvent::Connected); } } @@ -191,12 +191,11 @@ impl ChannelBuffer { operation, is_local: true, } => { - if *ZED_ALWAYS_ACTIVE { - if let language::Operation::UpdateSelections { selections, .. } = operation { - if selections.is_empty() { - return; - } - } + if *ZED_ALWAYS_ACTIVE + && let language::Operation::UpdateSelections { selections, .. } = operation + && selections.is_empty() + { + return; } let operation = language::proto::serialize_operation(operation); self.client diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index 4ac37ffd14..baf23ac39f 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -329,24 +329,24 @@ impl ChannelChat { loop { let step = chat .update(&mut cx, |chat, cx| { - if let Some(first_id) = chat.first_loaded_message_id() { - if first_id <= message_id { - let mut cursor = chat - .messages - .cursor::>(&()); - let message_id = ChannelMessageId::Saved(message_id); - cursor.seek(&message_id, Bias::Left); - return ControlFlow::Break( - if cursor - .item() - .map_or(false, |message| message.id == message_id) - { - Some(cursor.start().1.0) - } else { - None - }, - ); - } + if let Some(first_id) = chat.first_loaded_message_id() + && first_id <= message_id + { + let mut cursor = chat + .messages + .cursor::>(&()); + let message_id = ChannelMessageId::Saved(message_id); + cursor.seek(&message_id, Bias::Left); + return ControlFlow::Break( + if cursor + .item() + .is_some_and(|message| message.id == message_id) + { + Some(cursor.start().1.0) + } else { + None + }, + ); } ControlFlow::Continue(chat.load_more_messages(cx)) }) @@ -359,22 +359,21 @@ impl ChannelChat { } pub fn acknowledge_last_message(&mut self, cx: &mut Context) { - if let ChannelMessageId::Saved(latest_message_id) = self.messages.summary().max_id { - if self + if let ChannelMessageId::Saved(latest_message_id) = self.messages.summary().max_id + && self .last_acknowledged_id - .map_or(true, |acknowledged_id| acknowledged_id < latest_message_id) - { - self.rpc - .send(proto::AckChannelMessage { - channel_id: self.channel_id.0, - message_id: latest_message_id, - }) - .ok(); - self.last_acknowledged_id = Some(latest_message_id); - self.channel_store.update(cx, |store, cx| { - store.acknowledge_message_id(self.channel_id, latest_message_id, cx); - }); - } + .is_none_or(|acknowledged_id| acknowledged_id < latest_message_id) + { + self.rpc + .send(proto::AckChannelMessage { + channel_id: self.channel_id.0, + message_id: latest_message_id, + }) + .ok(); + self.last_acknowledged_id = Some(latest_message_id); + self.channel_store.update(cx, |store, cx| { + store.acknowledge_message_id(self.channel_id, latest_message_id, cx); + }); } } @@ -407,10 +406,10 @@ impl ChannelChat { let missing_ancestors = loaded_messages .iter() .filter_map(|message| { - if let Some(ancestor_id) = message.reply_to_message_id { - if !loaded_message_ids.contains(&ancestor_id) { - return Some(ancestor_id); - } + if let Some(ancestor_id) = message.reply_to_message_id + && !loaded_message_ids.contains(&ancestor_id) + { + return Some(ancestor_id); } None }) @@ -533,7 +532,7 @@ impl ChannelChat { message: TypedEnvelope, mut cx: AsyncApp, ) -> Result<()> { - let user_store = this.read_with(&mut cx, |this, _| this.user_store.clone())?; + let user_store = this.read_with(&cx, |this, _| this.user_store.clone())?; let message = message.payload.message.context("empty message")?; let message_id = message.id; @@ -565,7 +564,7 @@ impl ChannelChat { message: TypedEnvelope, mut cx: AsyncApp, ) -> Result<()> { - let user_store = this.read_with(&mut cx, |this, _| this.user_store.clone())?; + let user_store = this.read_with(&cx, |this, _| this.user_store.clone())?; let message = message.payload.message.context("empty message")?; let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?; @@ -613,7 +612,7 @@ impl ChannelChat { while let Some(message) = old_cursor.item() { let message_ix = old_cursor.start().1.0; if nonces.contains(&message.nonce) { - if ranges.last().map_or(false, |r| r.end == message_ix) { + if ranges.last().is_some_and(|r| r.end == message_ix) { ranges.last_mut().unwrap().end += 1; } else { ranges.push(message_ix..message_ix + 1); @@ -646,32 +645,32 @@ impl ChannelChat { fn message_removed(&mut self, id: u64, cx: &mut Context) { let mut cursor = self.messages.cursor::(&()); let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left); - if let Some(item) = cursor.item() { - if item.id == ChannelMessageId::Saved(id) { - let deleted_message_ix = messages.summary().count; - cursor.next(); - messages.append(cursor.suffix(), &()); - drop(cursor); - self.messages = messages; + if let Some(item) = cursor.item() + && item.id == ChannelMessageId::Saved(id) + { + let deleted_message_ix = messages.summary().count; + cursor.next(); + messages.append(cursor.suffix(), &()); + drop(cursor); + self.messages = messages; - // If the message that was deleted was the last acknowledged message, - // replace the acknowledged message with an earlier one. - self.channel_store.update(cx, |store, _| { - let summary = self.messages.summary(); - if summary.count == 0 { - store.set_acknowledged_message_id(self.channel_id, None); - } else if deleted_message_ix == summary.count { - if let ChannelMessageId::Saved(id) = summary.max_id { - store.set_acknowledged_message_id(self.channel_id, Some(id)); - } - } - }); + // If the message that was deleted was the last acknowledged message, + // replace the acknowledged message with an earlier one. + self.channel_store.update(cx, |store, _| { + let summary = self.messages.summary(); + if summary.count == 0 { + store.set_acknowledged_message_id(self.channel_id, None); + } else if deleted_message_ix == summary.count + && let ChannelMessageId::Saved(id) = summary.max_id + { + store.set_acknowledged_message_id(self.channel_id, Some(id)); + } + }); - cx.emit(ChannelChatEvent::MessagesUpdated { - old_range: deleted_message_ix..deleted_message_ix + 1, - new_count: 0, - }); - } + cx.emit(ChannelChatEvent::MessagesUpdated { + old_range: deleted_message_ix..deleted_message_ix + 1, + new_count: 0, + }); } } diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 4ad156b9fb..850a494613 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -262,13 +262,12 @@ impl ChannelStore { } } status = status_receiver.next().fuse() => { - if let Some(status) = status { - if status.is_connected() { + if let Some(status) = status + && status.is_connected() { this.update(cx, |this, _cx| { this.initialize(); }).ok(); } - } continue; } _ = timer => { @@ -336,10 +335,10 @@ impl ChannelStore { } pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &App) -> bool { - if let Some(buffer) = self.opened_buffers.get(&channel_id) { - if let OpenEntityHandle::Open(buffer) = buffer { - return buffer.upgrade().is_some(); - } + if let Some(buffer) = self.opened_buffers.get(&channel_id) + && let OpenEntityHandle::Open(buffer) = buffer + { + return buffer.upgrade().is_some(); } false } @@ -408,13 +407,12 @@ impl ChannelStore { pub fn last_acknowledge_message_id(&self, channel_id: ChannelId) -> Option { self.channel_states.get(&channel_id).and_then(|state| { - if let Some(last_message_id) = state.latest_chat_message { - if state + if let Some(last_message_id) = state.latest_chat_message + && state .last_acknowledged_message_id() .is_some_and(|id| id < last_message_id) - { - return state.last_acknowledged_message_id(); - } + { + return state.last_acknowledged_message_id(); } None @@ -570,16 +568,14 @@ impl ChannelStore { self.channel_index .by_id() .get(&channel_id) - .map_or(false, |channel| channel.is_root_channel()) + .is_some_and(|channel| channel.is_root_channel()) } pub fn is_public_channel(&self, channel_id: ChannelId) -> bool { self.channel_index .by_id() .get(&channel_id) - .map_or(false, |channel| { - channel.visibility == ChannelVisibility::Public - }) + .is_some_and(|channel| channel.visibility == ChannelVisibility::Public) } pub fn channel_capability(&self, channel_id: ChannelId) -> Capability { @@ -910,9 +906,9 @@ impl ChannelStore { async fn handle_update_channels( this: Entity, message: TypedEnvelope, - mut cx: AsyncApp, + cx: AsyncApp, ) -> Result<()> { - this.read_with(&mut cx, |this, _| { + this.read_with(&cx, |this, _| { this.update_channels_tx .unbounded_send(message.payload) .unwrap(); @@ -962,27 +958,27 @@ impl ChannelStore { self.disconnect_channel_buffers_task.take(); for chat in self.opened_chats.values() { - if let OpenEntityHandle::Open(chat) = chat { - if let Some(chat) = chat.upgrade() { - chat.update(cx, |chat, cx| { - chat.rejoin(cx); - }); - } + if let OpenEntityHandle::Open(chat) = chat + && let Some(chat) = chat.upgrade() + { + chat.update(cx, |chat, cx| { + chat.rejoin(cx); + }); } } let mut buffer_versions = Vec::new(); for buffer in self.opened_buffers.values() { - if let OpenEntityHandle::Open(buffer) = buffer { - if let Some(buffer) = buffer.upgrade() { - let channel_buffer = buffer.read(cx); - let buffer = channel_buffer.buffer().read(cx); - buffer_versions.push(proto::ChannelBufferVersion { - channel_id: channel_buffer.channel_id.0, - epoch: channel_buffer.epoch(), - version: language::proto::serialize_version(&buffer.version()), - }); - } + if let OpenEntityHandle::Open(buffer) = buffer + && let Some(buffer) = buffer.upgrade() + { + let channel_buffer = buffer.read(cx); + let buffer = channel_buffer.buffer().read(cx); + buffer_versions.push(proto::ChannelBufferVersion { + channel_id: channel_buffer.channel_id.0, + epoch: channel_buffer.epoch(), + version: language::proto::serialize_version(&buffer.version()), + }); } } @@ -1078,10 +1074,10 @@ impl ChannelStore { if let Some(this) = this.upgrade() { this.update(cx, |this, cx| { for (_, buffer) in &this.opened_buffers { - if let OpenEntityHandle::Open(buffer) = &buffer { - if let Some(buffer) = buffer.upgrade() { - buffer.update(cx, |buffer, cx| buffer.disconnect(cx)); - } + if let OpenEntityHandle::Open(buffer) = &buffer + && let Some(buffer) = buffer.upgrade() + { + buffer.update(cx, |buffer, cx| buffer.disconnect(cx)); } } }) @@ -1157,10 +1153,9 @@ impl ChannelStore { } if let Some(OpenEntityHandle::Open(buffer)) = self.opened_buffers.remove(&channel_id) + && let Some(buffer) = buffer.upgrade() { - if let Some(buffer) = buffer.upgrade() { - buffer.update(cx, ChannelBuffer::disconnect); - } + buffer.update(cx, ChannelBuffer::disconnect); } } } @@ -1170,12 +1165,11 @@ impl ChannelStore { let id = ChannelId(channel.id); let channel_changed = index.insert(channel); - if channel_changed { - if let Some(OpenEntityHandle::Open(buffer)) = self.opened_buffers.get(&id) { - if let Some(buffer) = buffer.upgrade() { - buffer.update(cx, ChannelBuffer::channel_changed); - } - } + if channel_changed + && let Some(OpenEntityHandle::Open(buffer)) = self.opened_buffers.get(&id) + && let Some(buffer) = buffer.upgrade() + { + buffer.update(cx, ChannelBuffer::channel_changed); } } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 67591167df..925d5ddefb 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -363,7 +363,7 @@ fn anonymous_fd(path: &str) -> Option { let fd: fd::RawFd = fd_str.parse().ok()?; let file = unsafe { fs::File::from_raw_fd(fd) }; - return Some(file); + Some(file) } #[cfg(any(target_os = "macos", target_os = "freebsd"))] { @@ -381,13 +381,13 @@ fn anonymous_fd(path: &str) -> Option { } let fd: fd::RawFd = fd_str.parse().ok()?; let file = unsafe { fs::File::from_raw_fd(fd) }; - return Some(file); + Some(file) } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))] { _ = path; // not implemented for bsd, windows. Could be, but isn't yet - return None; + None } } @@ -494,11 +494,11 @@ mod linux { Ok(Fork::Parent(_)) => Ok(()), Ok(Fork::Child) => { unsafe { std::env::set_var(FORCE_CLI_MODE_ENV_VAR_NAME, "") }; - if let Err(_) = fork::setsid() { + if fork::setsid().is_err() { eprintln!("failed to setsid: {}", std::io::Error::last_os_error()); process::exit(1); } - if let Err(_) = fork::close_fd() { + if fork::close_fd().is_err() { eprintln!("failed to close_fd: {}", std::io::Error::last_os_error()); } let error = @@ -518,11 +518,11 @@ mod linux { ) -> Result<(), std::io::Error> { for _ in 0..100 { thread::sleep(Duration::from_millis(10)); - if sock.connect_addr(&sock_addr).is_ok() { + if sock.connect_addr(sock_addr).is_ok() { return Ok(()); } } - sock.connect_addr(&sock_addr) + sock.connect_addr(sock_addr) } } } @@ -534,8 +534,8 @@ mod flatpak { use std::process::Command; use std::{env, process}; - const EXTRA_LIB_ENV_NAME: &'static str = "ZED_FLATPAK_LIB_PATH"; - const NO_ESCAPE_ENV_NAME: &'static str = "ZED_FLATPAK_NO_ESCAPE"; + const EXTRA_LIB_ENV_NAME: &str = "ZED_FLATPAK_LIB_PATH"; + const NO_ESCAPE_ENV_NAME: &str = "ZED_FLATPAK_NO_ESCAPE"; /// Adds bundled libraries to LD_LIBRARY_PATH if running under flatpak pub fn ld_extra_libs() { @@ -586,14 +586,11 @@ mod flatpak { pub fn set_bin_if_no_escape(mut args: super::Args) -> super::Args { if env::var(NO_ESCAPE_ENV_NAME).is_ok() - && env::var("FLATPAK_ID").map_or(false, |id| id.starts_with("dev.zed.Zed")) + && env::var("FLATPAK_ID").is_ok_and(|id| id.starts_with("dev.zed.Zed")) + && args.zed.is_none() { - if args.zed.is_none() { - args.zed = Some("/app/libexec/zed-editor".into()); - unsafe { - env::set_var("ZED_UPDATE_EXPLANATION", "Please use flatpak to update zed") - }; - } + args.zed = Some("/app/libexec/zed-editor".into()); + unsafe { env::set_var("ZED_UPDATE_EXPLANATION", "Please use flatpak to update zed") }; } args } diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 365625b445..5c6d1157fd 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -44,6 +44,7 @@ rpc = { workspace = true, features = ["gpui"] } schemars.workspace = true serde.workspace = true serde_json.workspace = true +serde_urlencoded.workspace = true settings.workspace = true sha2.workspace = true smol.workspace = true diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index f09c012a85..058a12417a 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -76,7 +76,7 @@ pub static ZED_APP_PATH: LazyLock> = LazyLock::new(|| std::env::var("ZED_APP_PATH").ok().map(PathBuf::from)); pub static ZED_ALWAYS_ACTIVE: LazyLock = - LazyLock::new(|| std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| !e.is_empty())); + LazyLock::new(|| std::env::var("ZED_ALWAYS_ACTIVE").is_ok_and(|e| !e.is_empty())); pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(500); pub const MAX_RECONNECTION_DELAY: Duration = Duration::from_secs(30); @@ -162,7 +162,7 @@ pub fn init(client: &Arc, cx: &mut App) { let client = client.clone(); move |_: &SignIn, cx| { if let Some(client) = client.upgrade() { - cx.spawn(async move |cx| client.sign_in_with_optional_connect(true, &cx).await) + cx.spawn(async move |cx| client.sign_in_with_optional_connect(true, cx).await) .detach_and_log_err(cx); } } @@ -173,7 +173,7 @@ pub fn init(client: &Arc, cx: &mut App) { move |_: &SignOut, cx| { if let Some(client) = client.upgrade() { cx.spawn(async move |cx| { - client.sign_out(&cx).await; + client.sign_out(cx).await; }) .detach(); } @@ -185,7 +185,7 @@ pub fn init(client: &Arc, cx: &mut App) { move |_: &Reconnect, cx| { if let Some(client) = client.upgrade() { cx.spawn(async move |cx| { - client.reconnect(&cx); + client.reconnect(cx); }) .detach(); } @@ -677,7 +677,7 @@ impl Client { let mut delay = INITIAL_RECONNECTION_DELAY; loop { - match client.connect(true, &cx).await { + match client.connect(true, cx).await { ConnectionResult::Timeout => { log::error!("client connect attempt timed out") } @@ -701,7 +701,7 @@ impl Client { Status::ReconnectionError { next_reconnection: Instant::now() + delay, }, - &cx, + cx, ); let jitter = Duration::from_millis(rng.gen_range(0..delay.as_millis() as u64)); @@ -864,22 +864,23 @@ impl Client { let mut credentials = None; let old_credentials = self.state.read().credentials.clone(); - if let Some(old_credentials) = old_credentials { - if self.validate_credentials(&old_credentials, cx).await? { - credentials = Some(old_credentials); - } + if let Some(old_credentials) = old_credentials + && self.validate_credentials(&old_credentials, cx).await? + { + credentials = Some(old_credentials); } - if credentials.is_none() && try_provider { - if let Some(stored_credentials) = self.credentials_provider.read_credentials(cx).await { - if self.validate_credentials(&stored_credentials, cx).await? { - credentials = Some(stored_credentials); - } else { - self.credentials_provider - .delete_credentials(cx) - .await - .log_err(); - } + if credentials.is_none() + && try_provider + && let Some(stored_credentials) = self.credentials_provider.read_credentials(cx).await + { + if self.validate_credentials(&stored_credentials, cx).await? { + credentials = Some(stored_credentials); + } else { + self.credentials_provider + .delete_credentials(cx) + .await + .log_err(); } } @@ -973,6 +974,11 @@ impl Client { try_provider: bool, cx: &AsyncApp, ) -> Result<()> { + // Don't try to sign in again if we're already connected to Collab, as it will temporarily disconnect us. + if self.status().borrow().is_connected() { + return Ok(()); + } + let (is_staff_tx, is_staff_rx) = oneshot::channel::(); let mut is_staff_tx = Some(is_staff_tx); cx.update(|cx| { @@ -1151,7 +1157,7 @@ impl Client { let this = self.clone(); async move |cx| { while let Some(message) = incoming.next().await { - this.handle_message(message, &cx); + this.handle_message(message, cx); // Don't starve the main thread when receiving lots of messages at once. smol::future::yield_now().await; } @@ -1169,12 +1175,12 @@ impl Client { peer_id, }) { - this.set_status(Status::SignedOut, &cx); + this.set_status(Status::SignedOut, cx); } } Err(err) => { log::error!("connection error: {:?}", err); - this.set_status(Status::ConnectionLost, &cx); + this.set_status(Status::ConnectionLost, cx); } } }) @@ -1410,6 +1416,12 @@ impl Client { open_url_tx.send(url).log_err(); + #[derive(Deserialize)] + struct CallbackParams { + pub user_id: String, + pub access_token: String, + } + // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted // access token from the query params. // @@ -1420,17 +1432,13 @@ impl Client { for _ in 0..100 { if let Some(req) = server.recv_timeout(Duration::from_secs(1))? { let path = req.url(); - let mut user_id = None; - let mut access_token = None; let url = Url::parse(&format!("http://example.com{}", path)) .context("failed to parse login notification url")?; - for (key, value) in url.query_pairs() { - if key == "access_token" { - access_token = Some(value.to_string()); - } else if key == "user_id" { - user_id = Some(value.to_string()); - } - } + let callback_params: CallbackParams = + serde_urlencoded::from_str(url.query().unwrap_or_default()) + .context( + "failed to parse sign-in callback query parameters", + )?; let post_auth_url = http.build_url("/native_app_signin_succeeded"); @@ -1445,8 +1453,8 @@ impl Client { ) .context("failed to respond to login http request")?; return Ok(( - user_id.context("missing user_id parameter")?, - access_token.context("missing access_token parameter")?, + callback_params.user_id, + callback_params.access_token, )); } } @@ -2065,8 +2073,8 @@ mod tests { let (done_tx1, done_rx1) = smol::channel::unbounded(); let (done_tx2, done_rx2) = smol::channel::unbounded(); AnyProtoClient::from(client.clone()).add_entity_message_handler( - move |entity: Entity, _: TypedEnvelope, mut cx| { - match entity.read_with(&mut cx, |entity, _| entity.id).unwrap() { + move |entity: Entity, _: TypedEnvelope, cx| { + match entity.read_with(&cx, |entity, _| entity.id).unwrap() { 1 => done_tx1.try_send(()).unwrap(), 2 => done_tx2.try_send(()).unwrap(), _ => unreachable!(), @@ -2090,17 +2098,17 @@ mod tests { let _subscription1 = client .subscribe_to_entity(1) .unwrap() - .set_entity(&entity1, &mut cx.to_async()); + .set_entity(&entity1, &cx.to_async()); let _subscription2 = client .subscribe_to_entity(2) .unwrap() - .set_entity(&entity2, &mut cx.to_async()); + .set_entity(&entity2, &cx.to_async()); // Ensure dropping a subscription for the same entity type still allows receiving of // messages for other entity IDs of the same type. let subscription3 = client .subscribe_to_entity(3) .unwrap() - .set_entity(&entity3, &mut cx.to_async()); + .set_entity(&entity3, &cx.to_async()); drop(subscription3); server.send(proto::JoinProject { diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index da7f50076b..2599be9b16 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -332,9 +332,9 @@ impl UserStore { async fn handle_update_contacts( this: Entity, message: TypedEnvelope, - mut cx: AsyncApp, + cx: AsyncApp, ) -> Result<()> { - this.read_with(&mut cx, |this, _| { + this.read_with(&cx, |this, _| { this.update_contacts_tx .unbounded_send(UpdateContacts::Update(message.payload)) .unwrap(); @@ -848,7 +848,7 @@ impl UserStore { pub fn has_accepted_terms_of_service(&self) -> bool { self.accepted_tos_at - .map_or(false, |accepted_tos_at| accepted_tos_at.is_some()) + .is_some_and(|accepted_tos_at| accepted_tos_at.is_some()) } pub fn accept_terms_of_service(&self, cx: &Context) -> Task> { @@ -894,10 +894,10 @@ impl UserStore { let mut ret = Vec::with_capacity(users.len()); for user in users { let user = User::new(user); - if let Some(old) = self.users.insert(user.id, user.clone()) { - if old.github_login != user.github_login { - self.by_github_login.remove(&old.github_login); - } + if let Some(old) = self.users.insert(user.id, user.clone()) + && old.github_login != user.github_login + { + self.by_github_login.remove(&old.github_login); } self.by_github_login .insert(user.github_login.clone(), user.id); diff --git a/crates/cloud_api_client/src/cloud_api_client.rs b/crates/cloud_api_client/src/cloud_api_client.rs index ef9a1a9a55..92417d8319 100644 --- a/crates/cloud_api_client/src/cloud_api_client.rs +++ b/crates/cloud_api_client/src/cloud_api_client.rs @@ -205,12 +205,12 @@ impl CloudApiClient { let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; if response.status() == StatusCode::UNAUTHORIZED { - return Ok(false); + Ok(false) } else { - return Err(anyhow!( + Err(anyhow!( "Failed to get authenticated user.\nStatus: {:?}\nBody: {body}", response.status() - )); + )) } } } diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 170ac7b0a2..43581fd942 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -116,6 +116,7 @@ CREATE TABLE "project_repositories" ( "scan_id" INTEGER NOT NULL, "is_deleted" BOOL NOT NULL, "current_merge_conflicts" VARCHAR, + "merge_message" VARCHAR, "branch_summary" VARCHAR, "head_commit_details" VARCHAR, PRIMARY KEY (project_id, id) diff --git a/crates/collab/migrations/20250818192156_add_git_merge_message.sql b/crates/collab/migrations/20250818192156_add_git_merge_message.sql new file mode 100644 index 0000000000..335ea2f824 --- /dev/null +++ b/crates/collab/migrations/20250818192156_add_git_merge_message.sql @@ -0,0 +1 @@ +ALTER TABLE "project_repositories" ADD COLUMN "merge_message" VARCHAR; diff --git a/crates/collab/migrations/20250819022421_add_orb_subscription_id_to_billing_subscriptions.sql b/crates/collab/migrations/20250819022421_add_orb_subscription_id_to_billing_subscriptions.sql new file mode 100644 index 0000000000..317f6a7653 --- /dev/null +++ b/crates/collab/migrations/20250819022421_add_orb_subscription_id_to_billing_subscriptions.sql @@ -0,0 +1,2 @@ +alter table billing_subscriptions + add column orb_subscription_id text; diff --git a/crates/collab/migrations/20250819225916_make_stripe_fields_optional_on_billing_subscription.sql b/crates/collab/migrations/20250819225916_make_stripe_fields_optional_on_billing_subscription.sql new file mode 100644 index 0000000000..cf3b79da60 --- /dev/null +++ b/crates/collab/migrations/20250819225916_make_stripe_fields_optional_on_billing_subscription.sql @@ -0,0 +1,3 @@ +alter table billing_subscriptions + alter column stripe_subscription_id drop not null, + alter column stripe_subscription_status drop not null; diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index cd1dc42e64..c500872fd7 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -149,35 +149,35 @@ pub async fn post_crash( "crash report" ); - if let Some(kinesis_client) = app.kinesis_client.clone() { - if let Some(stream) = app.config.kinesis_stream.clone() { - let properties = json!({ - "app_version": report.header.app_version, - "os_version": report.header.os_version, - "os_name": "macOS", - "bundle_id": report.header.bundle_id, - "incident_id": report.header.incident_id, - "installation_id": installation_id, - "description": description, - "backtrace": summary, - }); - let row = SnowflakeRow::new( - "Crash Reported", - None, - false, - Some(installation_id), - properties, - ); - let data = serde_json::to_vec(&row)?; - kinesis_client - .put_record() - .stream_name(stream) - .partition_key(row.insert_id.unwrap_or_default()) - .data(data.into()) - .send() - .await - .log_err(); - } + if let Some(kinesis_client) = app.kinesis_client.clone() + && let Some(stream) = app.config.kinesis_stream.clone() + { + let properties = json!({ + "app_version": report.header.app_version, + "os_version": report.header.os_version, + "os_name": "macOS", + "bundle_id": report.header.bundle_id, + "incident_id": report.header.incident_id, + "installation_id": installation_id, + "description": description, + "backtrace": summary, + }); + let row = SnowflakeRow::new( + "Crash Reported", + None, + false, + Some(installation_id), + properties, + ); + let data = serde_json::to_vec(&row)?; + kinesis_client + .put_record() + .stream_name(stream) + .partition_key(row.insert_id.unwrap_or_default()) + .data(data.into()) + .send() + .await + .log_err(); } if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() { @@ -359,34 +359,34 @@ pub async fn post_panic( "panic report" ); - if let Some(kinesis_client) = app.kinesis_client.clone() { - if let Some(stream) = app.config.kinesis_stream.clone() { - let properties = json!({ - "app_version": panic.app_version, - "os_name": panic.os_name, - "os_version": panic.os_version, - "incident_id": incident_id, - "installation_id": panic.installation_id, - "description": panic.payload, - "backtrace": backtrace, - }); - let row = SnowflakeRow::new( - "Panic Reported", - None, - false, - panic.installation_id.clone(), - properties, - ); - let data = serde_json::to_vec(&row)?; - kinesis_client - .put_record() - .stream_name(stream) - .partition_key(row.insert_id.unwrap_or_default()) - .data(data.into()) - .send() - .await - .log_err(); - } + if let Some(kinesis_client) = app.kinesis_client.clone() + && let Some(stream) = app.config.kinesis_stream.clone() + { + let properties = json!({ + "app_version": panic.app_version, + "os_name": panic.os_name, + "os_version": panic.os_version, + "incident_id": incident_id, + "installation_id": panic.installation_id, + "description": panic.payload, + "backtrace": backtrace, + }); + let row = SnowflakeRow::new( + "Panic Reported", + None, + false, + panic.installation_id.clone(), + properties, + ); + let data = serde_json::to_vec(&row)?; + kinesis_client + .put_record() + .stream_name(stream) + .partition_key(row.insert_id.unwrap_or_default()) + .data(data.into()) + .send() + .await + .log_err(); } if !report_to_slack(&panic) { @@ -518,31 +518,31 @@ pub async fn post_events( let first_event_at = chrono::Utc::now() - chrono::Duration::milliseconds(last_event.milliseconds_since_first_event); - if let Some(kinesis_client) = app.kinesis_client.clone() { - if let Some(stream) = app.config.kinesis_stream.clone() { - let mut request = kinesis_client.put_records().stream_name(stream); - let mut has_records = false; - for row in for_snowflake( - request_body.clone(), - first_event_at, - country_code.clone(), - checksum_matched, - ) { - if let Some(data) = serde_json::to_vec(&row).log_err() { - request = request.records( - aws_sdk_kinesis::types::PutRecordsRequestEntry::builder() - .partition_key(request_body.system_id.clone().unwrap_or_default()) - .data(data.into()) - .build() - .unwrap(), - ); - has_records = true; - } - } - if has_records { - request.send().await.log_err(); + if let Some(kinesis_client) = app.kinesis_client.clone() + && let Some(stream) = app.config.kinesis_stream.clone() + { + let mut request = kinesis_client.put_records().stream_name(stream); + let mut has_records = false; + for row in for_snowflake( + request_body.clone(), + first_event_at, + country_code.clone(), + checksum_matched, + ) { + if let Some(data) = serde_json::to_vec(&row).log_err() { + request = request.records( + aws_sdk_kinesis::types::PutRecordsRequestEntry::builder() + .partition_key(request_body.system_id.clone().unwrap_or_default()) + .data(data.into()) + .build() + .unwrap(), + ); + has_records = true; } } + if has_records { + request.send().await.log_err(); + } }; Ok(()) diff --git a/crates/collab/src/api/extensions.rs b/crates/collab/src/api/extensions.rs index 9170c39e47..1ace433db2 100644 --- a/crates/collab/src/api/extensions.rs +++ b/crates/collab/src/api/extensions.rs @@ -337,8 +337,7 @@ async fn fetch_extensions_from_blob_store( if known_versions .binary_search_by_key(&published_version, |known_version| known_version) .is_err() - { - if let Some(extension) = fetch_extension_manifest( + && let Some(extension) = fetch_extension_manifest( blob_store_client, blob_store_bucket, extension_id, @@ -346,12 +345,11 @@ async fn fetch_extensions_from_blob_store( ) .await .log_err() - { - new_versions - .entry(extension_id) - .or_default() - .push(extension); - } + { + new_versions + .entry(extension_id) + .or_default() + .push(extension); } } } diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index 00f37c6758..5a2a1329bb 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -79,27 +79,27 @@ pub async fn validate_header(mut req: Request, next: Next) -> impl Into verify_access_token(access_token, user_id, &state.db).await }; - if let Ok(validate_result) = validate_result { - if validate_result.is_valid { - let user = state - .db - .get_user_by_id(user_id) - .await? - .with_context(|| format!("user {user_id} not found"))?; + if let Ok(validate_result) = validate_result + && validate_result.is_valid + { + let user = state + .db + .get_user_by_id(user_id) + .await? + .with_context(|| format!("user {user_id} not found"))?; - if let Some(impersonator_id) = validate_result.impersonator_id { - let admin = state - .db - .get_user_by_id(impersonator_id) - .await? - .with_context(|| format!("user {impersonator_id} not found"))?; - req.extensions_mut() - .insert(Principal::Impersonated { user, admin }); - } else { - req.extensions_mut().insert(Principal::User(user)); - }; - return Ok::<_, Error>(next.run(req).await); - } + if let Some(impersonator_id) = validate_result.impersonator_id { + let admin = state + .db + .get_user_by_id(impersonator_id) + .await? + .with_context(|| format!("user {impersonator_id} not found"))?; + req.extensions_mut() + .insert(Principal::Impersonated { user, admin }); + } else { + req.extensions_mut().insert(Principal::User(user)); + }; + return Ok::<_, Error>(next.run(req).await); } Err(Error::http( diff --git a/crates/collab/src/db/queries/extensions.rs b/crates/collab/src/db/queries/extensions.rs index 7d8aad2be4..f218ff2850 100644 --- a/crates/collab/src/db/queries/extensions.rs +++ b/crates/collab/src/db/queries/extensions.rs @@ -87,10 +87,10 @@ impl Database { continue; }; - if let Some((_, max_extension_version)) = &max_versions.get(&version.extension_id) { - if max_extension_version > &extension_version { - continue; - } + if let Some((_, max_extension_version)) = &max_versions.get(&version.extension_id) + && max_extension_version > &extension_version + { + continue; } if let Some(constraints) = constraints { @@ -331,10 +331,10 @@ impl Database { .exec_without_returning(&*tx) .await?; - if let Ok(db_version) = semver::Version::parse(&extension.latest_version) { - if db_version >= latest_version.version { - continue; - } + if let Ok(db_version) = semver::Version::parse(&extension.latest_version) + && db_version >= latest_version.version + { + continue; } let mut extension = extension.into_active_model(); diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 82f74d910b..393f2c80f8 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -349,11 +349,11 @@ impl Database { serde_json::to_string(&repository.current_merge_conflicts) .unwrap(), )), - - // Old clients do not use abs path, entry ids or head_commit_details. + // Old clients do not use abs path, entry ids, head_commit_details, or merge_message. abs_path: ActiveValue::set(String::new()), entry_ids: ActiveValue::set("[]".into()), head_commit_details: ActiveValue::set(None), + merge_message: ActiveValue::set(None), } }), ) @@ -502,6 +502,7 @@ impl Database { current_merge_conflicts: ActiveValue::Set(Some( serde_json::to_string(&update.current_merge_conflicts).unwrap(), )), + merge_message: ActiveValue::set(update.merge_message.clone()), }) .on_conflict( OnConflict::columns([ @@ -515,6 +516,7 @@ impl Database { project_repository::Column::AbsPath, project_repository::Column::CurrentMergeConflicts, project_repository::Column::HeadCommitDetails, + project_repository::Column::MergeMessage, ]) .to_owned(), ) @@ -943,21 +945,21 @@ impl Database { let current_merge_conflicts = db_repository_entry .current_merge_conflicts .as_ref() - .map(|conflicts| serde_json::from_str(&conflicts)) + .map(|conflicts| serde_json::from_str(conflicts)) .transpose()? .unwrap_or_default(); let branch_summary = db_repository_entry .branch_summary .as_ref() - .map(|branch_summary| serde_json::from_str(&branch_summary)) + .map(|branch_summary| serde_json::from_str(branch_summary)) .transpose()? .unwrap_or_default(); let head_commit_details = db_repository_entry .head_commit_details .as_ref() - .map(|head_commit_details| serde_json::from_str(&head_commit_details)) + .map(|head_commit_details| serde_json::from_str(head_commit_details)) .transpose()? .unwrap_or_default(); @@ -990,6 +992,7 @@ impl Database { head_commit_details, scan_id: db_repository_entry.scan_id as u64, is_last_update: true, + merge_message: db_repository_entry.merge_message, }); } } @@ -1318,10 +1321,10 @@ impl Database { .await?; let mut connection_ids = HashSet::default(); - if let Some(host_connection) = project.host_connection().log_err() { - if !exclude_dev_server { - connection_ids.insert(host_connection); - } + if let Some(host_connection) = project.host_connection().log_err() + && !exclude_dev_server + { + connection_ids.insert(host_connection); } while let Some(collaborator) = collaborators.next().await { diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index c63d7133be..9e7cabf9b2 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -746,21 +746,21 @@ impl Database { let current_merge_conflicts = db_repository .current_merge_conflicts .as_ref() - .map(|conflicts| serde_json::from_str(&conflicts)) + .map(|conflicts| serde_json::from_str(conflicts)) .transpose()? .unwrap_or_default(); let branch_summary = db_repository .branch_summary .as_ref() - .map(|branch_summary| serde_json::from_str(&branch_summary)) + .map(|branch_summary| serde_json::from_str(branch_summary)) .transpose()? .unwrap_or_default(); let head_commit_details = db_repository .head_commit_details .as_ref() - .map(|head_commit_details| serde_json::from_str(&head_commit_details)) + .map(|head_commit_details| serde_json::from_str(head_commit_details)) .transpose()? .unwrap_or_default(); @@ -793,6 +793,7 @@ impl Database { abs_path: db_repository.abs_path, scan_id: db_repository.scan_id as u64, is_last_update: true, + merge_message: db_repository.merge_message, }); } } diff --git a/crates/collab/src/db/tables/project_repository.rs b/crates/collab/src/db/tables/project_repository.rs index 665e87cd1f..eb653ecee3 100644 --- a/crates/collab/src/db/tables/project_repository.rs +++ b/crates/collab/src/db/tables/project_repository.rs @@ -16,6 +16,8 @@ pub struct Model { pub is_deleted: bool, // JSON array typed string pub current_merge_conflicts: Option, + // The suggested merge commit message + pub merge_message: Option, // A JSON object representing the current Branch values pub branch_summary: Option, // A JSON object representing the current Head commit values diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index ef749ac9b7..01f553edf2 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -616,10 +616,10 @@ impl Server { } } - if let Some(live_kit) = livekit_client.as_ref() { - if delete_livekit_room { - live_kit.delete_room(livekit_room).await.trace_err(); - } + if let Some(live_kit) = livekit_client.as_ref() + && delete_livekit_room + { + live_kit.delete_room(livekit_room).await.trace_err(); } } } @@ -1015,47 +1015,47 @@ impl Server { inviter_id: UserId, invitee_id: UserId, ) -> Result<()> { - if let Some(user) = self.app_state.db.get_user_by_id(inviter_id).await? { - if let Some(code) = &user.invite_code { - let pool = self.connection_pool.lock(); - let invitee_contact = contact_for_user(invitee_id, false, &pool); - for connection_id in pool.user_connection_ids(inviter_id) { - self.peer.send( - connection_id, - proto::UpdateContacts { - contacts: vec![invitee_contact.clone()], - ..Default::default() - }, - )?; - self.peer.send( - connection_id, - proto::UpdateInviteInfo { - url: format!("{}{}", self.app_state.config.invite_link_prefix, &code), - count: user.invite_count as u32, - }, - )?; - } + if let Some(user) = self.app_state.db.get_user_by_id(inviter_id).await? + && let Some(code) = &user.invite_code + { + let pool = self.connection_pool.lock(); + let invitee_contact = contact_for_user(invitee_id, false, &pool); + for connection_id in pool.user_connection_ids(inviter_id) { + self.peer.send( + connection_id, + proto::UpdateContacts { + contacts: vec![invitee_contact.clone()], + ..Default::default() + }, + )?; + self.peer.send( + connection_id, + proto::UpdateInviteInfo { + url: format!("{}{}", self.app_state.config.invite_link_prefix, &code), + count: user.invite_count as u32, + }, + )?; } } Ok(()) } pub async fn invite_count_updated(self: &Arc, user_id: UserId) -> Result<()> { - if let Some(user) = self.app_state.db.get_user_by_id(user_id).await? { - if let Some(invite_code) = &user.invite_code { - let pool = self.connection_pool.lock(); - for connection_id in pool.user_connection_ids(user_id) { - self.peer.send( - connection_id, - proto::UpdateInviteInfo { - url: format!( - "{}{}", - self.app_state.config.invite_link_prefix, invite_code - ), - count: user.invite_count as u32, - }, - )?; - } + if let Some(user) = self.app_state.db.get_user_by_id(user_id).await? + && let Some(invite_code) = &user.invite_code + { + let pool = self.connection_pool.lock(); + for connection_id in pool.user_connection_ids(user_id) { + self.peer.send( + connection_id, + proto::UpdateInviteInfo { + url: format!( + "{}{}", + self.app_state.config.invite_link_prefix, invite_code + ), + count: user.invite_count as u32, + }, + )?; } } Ok(()) @@ -1101,10 +1101,10 @@ fn broadcast( F: FnMut(ConnectionId) -> anyhow::Result<()>, { for receiver_id in receiver_ids { - if Some(receiver_id) != sender_id { - if let Err(error) = f(receiver_id) { - tracing::error!("failed to send to {:?} {}", receiver_id, error); - } + if Some(receiver_id) != sender_id + && let Err(error) = f(receiver_id) + { + tracing::error!("failed to send to {:?} {}", receiver_id, error); } } } @@ -2294,11 +2294,10 @@ async fn update_language_server( let db = session.db().await; if let Some(proto::update_language_server::Variant::MetadataUpdated(update)) = &request.variant + && let Some(capabilities) = update.capabilities.clone() { - if let Some(capabilities) = update.capabilities.clone() { - db.update_server_capabilities(project_id, request.language_server_id, capabilities) - .await?; - } + db.update_server_capabilities(project_id, request.language_server_id, capabilities) + .await?; } let project_connection_ids = db diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 5a2c40b890..930e635dd8 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -4970,7 +4970,7 @@ async fn test_references( "Rust", FakeLspAdapter { name: "my-fake-lsp-adapter", - capabilities: capabilities, + capabilities, ..FakeLspAdapter::default() }, ); diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 4d94d041b9..cd4cf69f60 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -304,7 +304,7 @@ impl RandomizedTest for ProjectCollaborationTest { let worktree = worktree.read(cx); worktree.is_visible() && worktree.entries(false, 0).any(|e| e.is_file()) - && worktree.root_entry().map_or(false, |e| e.is_dir()) + && worktree.root_entry().is_some_and(|e| e.is_dir()) }) .choose(rng) }); @@ -1162,8 +1162,8 @@ impl RandomizedTest for ProjectCollaborationTest { Some((project, cx)) }); - if !guest_project.is_disconnected(cx) { - if let Some((host_project, host_cx)) = host_project { + if !guest_project.is_disconnected(cx) + && let Some((host_project, host_cx)) = host_project { let host_worktree_snapshots = host_project.read_with(host_cx, |host_project, cx| { host_project @@ -1235,7 +1235,6 @@ impl RandomizedTest for ProjectCollaborationTest { ); } } - } for buffer in guest_project.opened_buffers(cx) { let buffer = buffer.read(cx); diff --git a/crates/collab/src/tests/randomized_test_helpers.rs b/crates/collab/src/tests/randomized_test_helpers.rs index cabf10cfbc..d6c299a6a9 100644 --- a/crates/collab/src/tests/randomized_test_helpers.rs +++ b/crates/collab/src/tests/randomized_test_helpers.rs @@ -198,11 +198,11 @@ pub async fn run_randomized_test( } pub fn save_randomized_test_plan() { - if let Some(serialize_plan) = LAST_PLAN.lock().take() { - if let Some(path) = plan_save_path() { - eprintln!("saved test plan to path {:?}", path); - std::fs::write(path, serialize_plan()).unwrap(); - } + if let Some(serialize_plan) = LAST_PLAN.lock().take() + && let Some(path) = plan_save_path() + { + eprintln!("saved test plan to path {:?}", path); + std::fs::write(path, serialize_plan()).unwrap(); } } @@ -290,10 +290,9 @@ impl TestPlan { if let StoredOperation::Client { user_id, batch_id, .. } = operation + && batch_id == current_batch_id { - if batch_id == current_batch_id { - return Some(user_id); - } + return Some(user_id); } None })); @@ -366,10 +365,9 @@ impl TestPlan { }, applied, ) = stored_operation + && user_id == ¤t_user_id { - if user_id == ¤t_user_id { - return Some((operation.clone(), applied.clone())); - } + return Some((operation.clone(), applied.clone())); } } None @@ -550,11 +548,11 @@ impl TestPlan { .unwrap(); let pool = server.connection_pool.lock(); for contact in contacts { - if let db::Contact::Accepted { user_id, busy, .. } = contact { - if user_id == removed_user_id { - assert!(!pool.is_user_online(user_id)); - assert!(!busy); - } + if let db::Contact::Accepted { user_id, busy, .. } = contact + && user_id == removed_user_id + { + assert!(!pool.is_user_online(user_id)); + assert!(!busy); } } } diff --git a/crates/collab/src/user_backfiller.rs b/crates/collab/src/user_backfiller.rs index 71b99a3d4c..569a298c9c 100644 --- a/crates/collab/src/user_backfiller.rs +++ b/crates/collab/src/user_backfiller.rs @@ -130,17 +130,17 @@ impl UserBackfiller { .and_then(|value| value.parse::().ok()) .and_then(|value| DateTime::from_timestamp(value, 0)); - if rate_limit_remaining == Some(0) { - if let Some(reset_at) = rate_limit_reset { - let now = Utc::now(); - if reset_at > now { - let sleep_duration = reset_at - now; - log::info!( - "rate limit reached. Sleeping for {} seconds", - sleep_duration.num_seconds() - ); - self.executor.sleep(sleep_duration.to_std().unwrap()).await; - } + if rate_limit_remaining == Some(0) + && let Some(reset_at) = rate_limit_reset + { + let now = Utc::now(); + if reset_at > now { + let sleep_duration = reset_at - now; + log::info!( + "rate limit reached. Sleeping for {} seconds", + sleep_duration.num_seconds() + ); + self.executor.sleep(sleep_duration.to_std().unwrap()).await; } } diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index b86d72d92f..9993c0841c 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -107,43 +107,32 @@ impl ChannelView { .find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id); // If this channel buffer is already open in this pane, just return it. - if let Some(existing_view) = existing_view.clone() { - if existing_view.read(cx).channel_buffer == channel_view.read(cx).channel_buffer - { - if let Some(link_position) = link_position { - existing_view.update(cx, |channel_view, cx| { - channel_view.focus_position_from_link( - link_position, - true, - window, - cx, - ) - }); - } - return existing_view; + if let Some(existing_view) = existing_view.clone() + && existing_view.read(cx).channel_buffer == channel_view.read(cx).channel_buffer + { + if let Some(link_position) = link_position { + existing_view.update(cx, |channel_view, cx| { + channel_view.focus_position_from_link(link_position, true, window, cx) + }); } + return existing_view; } // If the pane contained a disconnected view for this channel buffer, // replace that. - if let Some(existing_item) = existing_view { - if let Some(ix) = pane.index_for_item(&existing_item) { - pane.close_item_by_id( - existing_item.entity_id(), - SaveIntent::Skip, - window, - cx, - ) + if let Some(existing_item) = existing_view + && let Some(ix) = pane.index_for_item(&existing_item) + { + pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, window, cx) .detach(); - pane.add_item( - Box::new(channel_view.clone()), - true, - true, - Some(ix), - window, - cx, - ); - } + pane.add_item( + Box::new(channel_view.clone()), + true, + true, + Some(ix), + window, + cx, + ); } if let Some(link_position) = link_position { @@ -259,26 +248,21 @@ impl ChannelView { .editor .update(cx, |editor, cx| editor.snapshot(window, cx)); - if let Some(outline) = snapshot.buffer_snapshot.outline(None) { - if let Some(item) = outline + if let Some(outline) = snapshot.buffer_snapshot.outline(None) + && let Some(item) = outline .items .iter() .find(|item| &Channel::slug(&item.text).to_lowercase() == &position) - { - self.editor.update(cx, |editor, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::focused()), - window, - cx, - |s| { - s.replace_cursors_with(|map| { - vec![item.range.start.to_display_point(map)] - }) - }, - ) - }); - return; - } + { + self.editor.update(cx, |editor, cx| { + editor.change_selections( + SelectionEffects::scroll(Autoscroll::focused()), + window, + cx, + |s| s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)]), + ) + }); + return; } if !first_attempt { diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 2bbaa8446c..5ed3907f6c 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -287,19 +287,20 @@ impl ChatPanel { } fn acknowledge_last_message(&mut self, cx: &mut Context) { - if self.active && self.is_scrolled_to_bottom { - if let Some((chat, _)) = &self.active_chat { - if let Some(channel_id) = self.channel_id(cx) { - self.last_acknowledged_message_id = self - .channel_store - .read(cx) - .last_acknowledge_message_id(channel_id); - } - - chat.update(cx, |chat, cx| { - chat.acknowledge_last_message(cx); - }); + if self.active + && self.is_scrolled_to_bottom + && let Some((chat, _)) = &self.active_chat + { + if let Some(channel_id) = self.channel_id(cx) { + self.last_acknowledged_message_id = self + .channel_store + .read(cx) + .last_acknowledge_message_id(channel_id); } + + chat.update(cx, |chat, cx| { + chat.acknowledge_last_message(cx); + }); } } @@ -405,14 +406,13 @@ impl ChatPanel { && last_message.id != this_message.id && duration_since_last_message < Duration::from_secs(5 * 60); - if let ChannelMessageId::Saved(id) = this_message.id { - if this_message + if let ChannelMessageId::Saved(id) = this_message.id + && this_message .mentions .iter() .any(|(_, user_id)| Some(*user_id) == self.client.user_id()) - { - active_chat.acknowledge_message(id); - } + { + active_chat.acknowledge_message(id); } (this_message, is_continuation_from_previous, is_admin) @@ -871,34 +871,33 @@ impl ChatPanel { scroll_to_message_id.or(this.last_acknowledged_message_id) })?; - if let Some(message_id) = scroll_to_message_id { - if let Some(item_ix) = + if let Some(message_id) = scroll_to_message_id + && let Some(item_ix) = ChannelChat::load_history_since_message(chat.clone(), message_id, cx.clone()) .await - { - this.update(cx, |this, cx| { - if let Some(highlight_message_id) = highlight_message_id { - let task = cx.spawn(async move |this, cx| { - cx.background_executor().timer(Duration::from_secs(2)).await; - this.update(cx, |this, cx| { - this.highlighted_message.take(); - cx.notify(); - }) - .ok(); - }); + { + this.update(cx, |this, cx| { + if let Some(highlight_message_id) = highlight_message_id { + let task = cx.spawn(async move |this, cx| { + cx.background_executor().timer(Duration::from_secs(2)).await; + this.update(cx, |this, cx| { + this.highlighted_message.take(); + cx.notify(); + }) + .ok(); + }); - this.highlighted_message = Some((highlight_message_id, task)); - } + this.highlighted_message = Some((highlight_message_id, task)); + } - if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) { - this.message_list.scroll_to(ListOffset { - item_ix, - offset_in_item: px(0.0), - }); - cx.notify(); - } - })?; - } + if this.active_chat.as_ref().is_some_and(|(c, _)| *c == chat) { + this.message_list.scroll_to(ListOffset { + item_ix, + offset_in_item: px(0.0), + }); + cx.notify(); + } + })?; } Ok(()) @@ -1187,7 +1186,7 @@ impl Panel for ChatPanel { let is_in_call = ActiveCall::global(cx) .read(cx) .room() - .map_or(false, |room| room.read(cx).contains_guests()); + .is_some_and(|room| room.read(cx).contains_guests()); self.active || is_in_call } diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 03d39cb8ce..5fead5bcf1 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -241,38 +241,36 @@ impl MessageEditor { ) -> Task>> { if let Some((start_anchor, query, candidates)) = self.collect_mention_candidates(buffer, end_anchor, cx) + && !candidates.is_empty() { - if !candidates.is_empty() { - return cx.spawn(async move |_, cx| { - let completion_response = Self::completions_for_candidates( - &cx, - query.as_str(), - &candidates, - start_anchor..end_anchor, - Self::completion_for_mention, - ) - .await; - Ok(vec![completion_response]) - }); - } + return cx.spawn(async move |_, cx| { + let completion_response = Self::completions_for_candidates( + cx, + query.as_str(), + &candidates, + start_anchor..end_anchor, + Self::completion_for_mention, + ) + .await; + Ok(vec![completion_response]) + }); } if let Some((start_anchor, query, candidates)) = self.collect_emoji_candidates(buffer, end_anchor, cx) + && !candidates.is_empty() { - if !candidates.is_empty() { - return cx.spawn(async move |_, cx| { - let completion_response = Self::completions_for_candidates( - &cx, - query.as_str(), - candidates, - start_anchor..end_anchor, - Self::completion_for_emoji, - ) - .await; - Ok(vec![completion_response]) - }); - } + return cx.spawn(async move |_, cx| { + let completion_response = Self::completions_for_candidates( + cx, + query.as_str(), + candidates, + start_anchor..end_anchor, + Self::completion_for_emoji, + ) + .await; + Ok(vec![completion_response]) + }); } Task::ready(Ok(vec![CompletionResponse { @@ -399,11 +397,10 @@ impl MessageEditor { ) -> Option<(Anchor, String, &'static [StringMatchCandidate])> { static EMOJI_FUZZY_MATCH_CANDIDATES: LazyLock> = LazyLock::new(|| { - let emojis = emojis::iter() + emojis::iter() .flat_map(|s| s.shortcodes()) .map(|emoji| StringMatchCandidate::new(0, emoji)) - .collect::>(); - emojis + .collect::>() }); let end_offset = end_anchor.to_offset(buffer.read(cx)); @@ -474,18 +471,17 @@ impl MessageEditor { for range in ranges { text.clear(); text.extend(buffer.text_for_range(range.clone())); - if let Some(username) = text.strip_prefix('@') { - if let Some(user) = this + if let Some(username) = text.strip_prefix('@') + && let Some(user) = this .user_store .read(cx) .cached_user_by_github_login(username) - { - let start = multi_buffer.anchor_after(range.start); - let end = multi_buffer.anchor_after(range.end); + { + let start = multi_buffer.anchor_after(range.start); + let end = multi_buffer.anchor_after(range.end); - mentioned_user_ids.push(user.id); - anchor_ranges.push(start..end); - } + mentioned_user_ids.push(user.id); + anchor_ranges.push(start..end); } } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index c2cc6a7ad5..0f785c1f90 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -311,10 +311,10 @@ impl CollabPanel { window, |this: &mut Self, _, event, window, cx| { if let editor::EditorEvent::Blurred = event { - if let Some(state) = &this.channel_editing_state { - if state.pending_name().is_some() { - return; - } + if let Some(state) = &this.channel_editing_state + && state.pending_name().is_some() + { + return; } this.take_editing_state(window, cx); this.update_entries(false, cx); @@ -491,11 +491,11 @@ impl CollabPanel { if !self.collapsed_sections.contains(&Section::ActiveCall) { let room = room.read(cx); - if query.is_empty() { - if let Some(channel_id) = room.channel_id() { - self.entries.push(ListEntry::ChannelNotes { channel_id }); - self.entries.push(ListEntry::ChannelChat { channel_id }); - } + if query.is_empty() + && let Some(channel_id) = room.channel_id() + { + self.entries.push(ListEntry::ChannelNotes { channel_id }); + self.entries.push(ListEntry::ChannelChat { channel_id }); } // Populate the active user. @@ -639,10 +639,10 @@ impl CollabPanel { &Default::default(), executor.clone(), )); - if let Some(state) = &self.channel_editing_state { - if matches!(state, ChannelEditingState::Create { location: None, .. }) { - self.entries.push(ListEntry::ChannelEditor { depth: 0 }); - } + if let Some(state) = &self.channel_editing_state + && matches!(state, ChannelEditingState::Create { location: None, .. }) + { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } let mut collapse_depth = None; for mat in matches { @@ -664,9 +664,7 @@ impl CollabPanel { let has_children = channel_store .channel_at_index(mat.candidate_id + 1) - .map_or(false, |next_channel| { - next_channel.parent_path.ends_with(&[channel.id]) - }); + .is_some_and(|next_channel| next_channel.parent_path.ends_with(&[channel.id])); match &self.channel_editing_state { Some(ChannelEditingState::Create { @@ -1125,7 +1123,7 @@ impl CollabPanel { } fn has_subchannels(&self, ix: usize) -> bool { - self.entries.get(ix).map_or(false, |entry| { + self.entries.get(ix).is_some_and(|entry| { if let ListEntry::Channel { has_children, .. } = entry { *has_children } else { @@ -1552,98 +1550,93 @@ impl CollabPanel { return; } - if let Some(selection) = self.selection { - if let Some(entry) = self.entries.get(selection) { - match entry { - ListEntry::Header(section) => match section { - Section::ActiveCall => Self::leave_call(window, cx), - Section::Channels => self.new_root_channel(window, cx), - Section::Contacts => self.toggle_contact_finder(window, cx), - Section::ContactRequests - | Section::Online - | Section::Offline - | Section::ChannelInvites => { - self.toggle_section_expanded(*section, cx); - } - }, - ListEntry::Contact { contact, calling } => { - if contact.online && !contact.busy && !calling { - self.call(contact.user.id, window, cx); - } + if let Some(selection) = self.selection + && let Some(entry) = self.entries.get(selection) + { + match entry { + ListEntry::Header(section) => match section { + Section::ActiveCall => Self::leave_call(window, cx), + Section::Channels => self.new_root_channel(window, cx), + Section::Contacts => self.toggle_contact_finder(window, cx), + Section::ContactRequests + | Section::Online + | Section::Offline + | Section::ChannelInvites => { + self.toggle_section_expanded(*section, cx); } - ListEntry::ParticipantProject { - project_id, - host_user_id, - .. - } => { - if let Some(workspace) = self.workspace.upgrade() { - let app_state = workspace.read(cx).app_state().clone(); - workspace::join_in_room_project( - *project_id, - *host_user_id, - app_state, - cx, - ) + }, + ListEntry::Contact { contact, calling } => { + if contact.online && !contact.busy && !calling { + self.call(contact.user.id, window, cx); + } + } + ListEntry::ParticipantProject { + project_id, + host_user_id, + .. + } => { + if let Some(workspace) = self.workspace.upgrade() { + let app_state = workspace.read(cx).app_state().clone(); + workspace::join_in_room_project(*project_id, *host_user_id, app_state, cx) .detach_and_prompt_err( "Failed to join project", window, cx, |_, _, _| None, ); - } } - ListEntry::ParticipantScreen { peer_id, .. } => { - let Some(peer_id) = peer_id else { - return; - }; - if let Some(workspace) = self.workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - workspace.open_shared_screen(*peer_id, window, cx) - }); - } - } - ListEntry::Channel { channel, .. } => { - let is_active = maybe!({ - let call_channel = ActiveCall::global(cx) - .read(cx) - .room()? - .read(cx) - .channel_id()?; - - Some(call_channel == channel.id) - }) - .unwrap_or(false); - if is_active { - self.open_channel_notes(channel.id, window, cx) - } else { - self.join_channel(channel.id, window, cx) - } - } - ListEntry::ContactPlaceholder => self.toggle_contact_finder(window, cx), - ListEntry::CallParticipant { user, peer_id, .. } => { - if Some(user) == self.user_store.read(cx).current_user().as_ref() { - Self::leave_call(window, cx); - } else if let Some(peer_id) = peer_id { - self.workspace - .update(cx, |workspace, cx| workspace.follow(*peer_id, window, cx)) - .ok(); - } - } - ListEntry::IncomingRequest(user) => { - self.respond_to_contact_request(user.id, true, window, cx) - } - ListEntry::ChannelInvite(channel) => { - self.respond_to_channel_invite(channel.id, true, cx) - } - ListEntry::ChannelNotes { channel_id } => { - self.open_channel_notes(*channel_id, window, cx) - } - ListEntry::ChannelChat { channel_id } => { - self.join_channel_chat(*channel_id, window, cx) - } - ListEntry::OutgoingRequest(_) => {} - ListEntry::ChannelEditor { .. } => {} } + ListEntry::ParticipantScreen { peer_id, .. } => { + let Some(peer_id) = peer_id else { + return; + }; + if let Some(workspace) = self.workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + workspace.open_shared_screen(*peer_id, window, cx) + }); + } + } + ListEntry::Channel { channel, .. } => { + let is_active = maybe!({ + let call_channel = ActiveCall::global(cx) + .read(cx) + .room()? + .read(cx) + .channel_id()?; + + Some(call_channel == channel.id) + }) + .unwrap_or(false); + if is_active { + self.open_channel_notes(channel.id, window, cx) + } else { + self.join_channel(channel.id, window, cx) + } + } + ListEntry::ContactPlaceholder => self.toggle_contact_finder(window, cx), + ListEntry::CallParticipant { user, peer_id, .. } => { + if Some(user) == self.user_store.read(cx).current_user().as_ref() { + Self::leave_call(window, cx); + } else if let Some(peer_id) = peer_id { + self.workspace + .update(cx, |workspace, cx| workspace.follow(*peer_id, window, cx)) + .ok(); + } + } + ListEntry::IncomingRequest(user) => { + self.respond_to_contact_request(user.id, true, window, cx) + } + ListEntry::ChannelInvite(channel) => { + self.respond_to_channel_invite(channel.id, true, cx) + } + ListEntry::ChannelNotes { channel_id } => { + self.open_channel_notes(*channel_id, window, cx) + } + ListEntry::ChannelChat { channel_id } => { + self.join_channel_chat(*channel_id, window, cx) + } + ListEntry::OutgoingRequest(_) => {} + ListEntry::ChannelEditor { .. } => {} } } } @@ -2317,7 +2310,7 @@ impl CollabPanel { let client = this.client.clone(); cx.spawn_in(window, async move |_, cx| { client - .connect(true, &cx) + .connect(true, cx) .await .into_response() .notify_async_err(cx); diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index a3420d603b..a900d585f8 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -121,13 +121,12 @@ impl NotificationPanel { let notification_list = ListState::new(0, ListAlignment::Top, px(1000.)); notification_list.set_scroll_handler(cx.listener( |this, event: &ListScrollEvent, _, cx| { - if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD { - if let Some(task) = this + if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD + && let Some(task) = this .notification_store .update(cx, |store, cx| store.load_more_notifications(false, cx)) - { - task.detach(); - } + { + task.detach(); } }, )); @@ -469,20 +468,19 @@ impl NotificationPanel { channel_id, .. } = notification.clone() + && let Some(workspace) = self.workspace.upgrade() { - if let Some(workspace) = self.workspace.upgrade() { - window.defer(cx, move |window, cx| { - workspace.update(cx, |workspace, cx| { - if let Some(panel) = workspace.focus_panel::(window, cx) { - panel.update(cx, |panel, cx| { - panel - .select_channel(ChannelId(channel_id), Some(message_id), cx) - .detach_and_log_err(cx); - }); - } - }); + window.defer(cx, move |window, cx| { + workspace.update(cx, |workspace, cx| { + if let Some(panel) = workspace.focus_panel::(window, cx) { + panel.update(cx, |panel, cx| { + panel + .select_channel(ChannelId(channel_id), Some(message_id), cx) + .detach_and_log_err(cx); + }); + } }); - } + }); } } @@ -491,18 +489,18 @@ impl NotificationPanel { return false; } - if let Notification::ChannelMessageMention { channel_id, .. } = ¬ification { - if let Some(workspace) = self.workspace.upgrade() { - return if let Some(panel) = workspace.read(cx).panel::(cx) { - let panel = panel.read(cx); - panel.is_scrolled_to_bottom() - && panel - .active_chat() - .map_or(false, |chat| chat.read(cx).channel_id.0 == *channel_id) - } else { - false - }; - } + if let Notification::ChannelMessageMention { channel_id, .. } = ¬ification + && let Some(workspace) = self.workspace.upgrade() + { + return if let Some(panel) = workspace.read(cx).panel::(cx) { + let panel = panel.read(cx); + panel.is_scrolled_to_bottom() + && panel + .active_chat() + .is_some_and(|chat| chat.read(cx).channel_id.0 == *channel_id) + } else { + false + }; } false @@ -582,16 +580,16 @@ impl NotificationPanel { } fn remove_toast(&mut self, notification_id: u64, cx: &mut Context) { - if let Some((current_id, _)) = &self.current_notification_toast { - if *current_id == notification_id { - self.current_notification_toast.take(); - self.workspace - .update(cx, |workspace, cx| { - let id = NotificationId::unique::(); - workspace.dismiss_notification(&id, cx) - }) - .ok(); - } + if let Some((current_id, _)) = &self.current_notification_toast + && *current_id == notification_id + { + self.current_notification_toast.take(); + self.workspace + .update(cx, |workspace, cx| { + let id = NotificationId::unique::(); + workspace.dismiss_notification(&id, cx) + }) + .ok(); } } @@ -643,7 +641,7 @@ impl Render for NotificationPanel { let client = client.clone(); window .spawn(cx, async move |cx| { - match client.connect(true, &cx).await { + match client.connect(true, cx).await { util::ConnectionResult::Timeout => { log::error!("Connection timeout"); } diff --git a/crates/context_server/src/client.rs b/crates/context_server/src/client.rs index 65283afa87..ccf7622d82 100644 --- a/crates/context_server/src/client.rs +++ b/crates/context_server/src/client.rs @@ -161,7 +161,7 @@ impl Client { working_directory: &Option, cx: AsyncApp, ) -> Result { - log::info!( + log::debug!( "starting context server (executable={:?}, args={:?})", binary.executable, &binary.args @@ -271,10 +271,10 @@ impl Client { ); } } else if let Ok(response) = serde_json::from_str::(&message) { - if let Some(handlers) = response_handlers.lock().as_mut() { - if let Some(handler) = handlers.remove(&response.id) { - handler(Ok(message.to_string())); - } + if let Some(handlers) = response_handlers.lock().as_mut() + && let Some(handler) = handlers.remove(&response.id) + { + handler(Ok(message.to_string())); } } else if let Ok(notification) = serde_json::from_str::(&message) { let mut notification_handlers = notification_handlers.lock(); @@ -295,7 +295,7 @@ impl Client { /// Continuously reads and logs any error messages from the server. async fn handle_err(transport: Arc) -> anyhow::Result<()> { while let Some(err) = transport.receive_err().next().await { - log::warn!("context server stderr: {}", err.trim()); + log::debug!("context server stderr: {}", err.trim()); } Ok(()) diff --git a/crates/context_server/src/context_server.rs b/crates/context_server/src/context_server.rs index 34fa29678d..9ca78138db 100644 --- a/crates/context_server/src/context_server.rs +++ b/crates/context_server/src/context_server.rs @@ -137,7 +137,7 @@ impl ContextServer { } async fn initialize(&self, client: Client) -> Result<()> { - log::info!("starting context server {}", self.id); + log::debug!("starting context server {}", self.id); let protocol = crate::protocol::ModelContextProtocol::new(client); let client_info = types::Implementation { name: "Zed".to_string(), diff --git a/crates/context_server/src/listener.rs b/crates/context_server/src/listener.rs index 0e85fb2129..1b44cefbd2 100644 --- a/crates/context_server/src/listener.rs +++ b/crates/context_server/src/listener.rs @@ -14,6 +14,7 @@ use serde::de::DeserializeOwned; use serde_json::{json, value::RawValue}; use smol::stream::StreamExt; use std::{ + any::TypeId, cell::RefCell, path::{Path, PathBuf}, rc::Rc, @@ -77,7 +78,7 @@ impl McpServer { socket_path, _server_task: server_task, tools, - handlers: handlers, + handlers, }) }) } @@ -87,18 +88,26 @@ impl McpServer { settings.inline_subschemas = true; let mut generator = settings.into_generator(); - let output_schema = generator.root_schema_for::(); - let unit_schema = generator.root_schema_for::(); + let input_schema = generator.root_schema_for::(); + + let description = input_schema + .get("description") + .and_then(|desc| desc.as_str()) + .map(|desc| desc.to_string()); + debug_assert!( + description.is_some(), + "Input schema struct must include a doc comment for the tool description" + ); let registered_tool = RegisteredTool { tool: Tool { name: T::NAME.into(), - description: Some(tool.description().into()), - input_schema: generator.root_schema_for::().into(), - output_schema: if output_schema == unit_schema { + description, + input_schema: input_schema.into(), + output_schema: if TypeId::of::() == TypeId::of::<()>() { None } else { - Some(output_schema.into()) + Some(generator.root_schema_for::().into()) }, annotations: Some(tool.annotations()), }, @@ -315,12 +324,12 @@ impl McpServer { Self::send_err( request_id, format!("Tool not found: {}", params.name), - &outgoing_tx, + outgoing_tx, ); } } Err(err) => { - Self::send_err(request_id, err.to_string(), &outgoing_tx); + Self::send_err(request_id, err.to_string(), outgoing_tx); } } } @@ -399,8 +408,6 @@ pub trait McpServerTool { const NAME: &'static str; - fn description(&self) -> &'static str; - fn annotations(&self) -> ToolAnnotations { ToolAnnotations { title: None, @@ -418,6 +425,7 @@ pub trait McpServerTool { ) -> impl Future>>; } +#[derive(Debug)] pub struct ToolResponse { pub content: Vec, pub structured_content: T, diff --git a/crates/context_server/src/types.rs b/crates/context_server/src/types.rs index 5fa2420a3d..03aca4f3ca 100644 --- a/crates/context_server/src/types.rs +++ b/crates/context_server/src/types.rs @@ -691,7 +691,7 @@ impl CallToolResponse { let mut text = String::new(); for chunk in &self.content { if let ToolResponseContent::Text { text: chunk } = chunk { - text.push_str(&chunk) + text.push_str(chunk) }; } text @@ -711,6 +711,16 @@ pub enum ToolResponseContent { Resource { resource: ResourceContents }, } +impl ToolResponseContent { + pub fn text(&self) -> Option<&str> { + if let ToolResponseContent::Text { text } = self { + Some(text) + } else { + None + } + } +} + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ListToolsResponse { diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index dcebeae721..1916853a69 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -608,15 +608,13 @@ impl Copilot { sign_in_status: status, .. }) = &mut this.server - { - if let SignInStatus::SigningIn { + && let SignInStatus::SigningIn { prompt: prompt_flow, .. } = status - { - *prompt_flow = Some(flow.clone()); - cx.notify(); - } + { + *prompt_flow = Some(flow.clone()); + cx.notify(); } })?; let response = lsp @@ -782,59 +780,58 @@ impl Copilot { event: &language::BufferEvent, cx: &mut Context, ) -> Result<()> { - if let Ok(server) = self.server.as_running() { - if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id()) - { - match event { - language::BufferEvent::Edited => { - drop(registered_buffer.report_changes(&buffer, cx)); - } - language::BufferEvent::Saved => { + if let Ok(server) = self.server.as_running() + && let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id()) + { + match event { + language::BufferEvent::Edited => { + drop(registered_buffer.report_changes(&buffer, cx)); + } + language::BufferEvent::Saved => { + server + .lsp + .notify::( + &lsp::DidSaveTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new( + registered_buffer.uri.clone(), + ), + text: None, + }, + )?; + } + language::BufferEvent::FileHandleChanged + | language::BufferEvent::LanguageChanged => { + let new_language_id = id_for_language(buffer.read(cx).language()); + let Ok(new_uri) = uri_for_buffer(&buffer, cx) else { + return Ok(()); + }; + if new_uri != registered_buffer.uri + || new_language_id != registered_buffer.language_id + { + let old_uri = mem::replace(&mut registered_buffer.uri, new_uri); + registered_buffer.language_id = new_language_id; server .lsp - .notify::( - &lsp::DidSaveTextDocumentParams { - text_document: lsp::TextDocumentIdentifier::new( + .notify::( + &lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new(old_uri), + }, + )?; + server + .lsp + .notify::( + &lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( registered_buffer.uri.clone(), + registered_buffer.language_id.clone(), + registered_buffer.snapshot_version, + registered_buffer.snapshot.text(), ), - text: None, }, )?; } - language::BufferEvent::FileHandleChanged - | language::BufferEvent::LanguageChanged => { - let new_language_id = id_for_language(buffer.read(cx).language()); - let Ok(new_uri) = uri_for_buffer(&buffer, cx) else { - return Ok(()); - }; - if new_uri != registered_buffer.uri - || new_language_id != registered_buffer.language_id - { - let old_uri = mem::replace(&mut registered_buffer.uri, new_uri); - registered_buffer.language_id = new_language_id; - server - .lsp - .notify::( - &lsp::DidCloseTextDocumentParams { - text_document: lsp::TextDocumentIdentifier::new(old_uri), - }, - )?; - server - .lsp - .notify::( - &lsp::DidOpenTextDocumentParams { - text_document: lsp::TextDocumentItem::new( - registered_buffer.uri.clone(), - registered_buffer.language_id.clone(), - registered_buffer.snapshot_version, - registered_buffer.snapshot.text(), - ), - }, - )?; - } - } - _ => {} } + _ => {} } } @@ -842,17 +839,17 @@ impl Copilot { } fn unregister_buffer(&mut self, buffer: &WeakEntity) { - if let Ok(server) = self.server.as_running() { - if let Some(buffer) = server.registered_buffers.remove(&buffer.entity_id()) { - server - .lsp - .notify::( - &lsp::DidCloseTextDocumentParams { - text_document: lsp::TextDocumentIdentifier::new(buffer.uri), - }, - ) - .ok(); - } + if let Ok(server) = self.server.as_running() + && let Some(buffer) = server.registered_buffers.remove(&buffer.entity_id()) + { + server + .lsp + .notify::( + &lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new(buffer.uri), + }, + ) + .ok(); } } diff --git a/crates/copilot/src/copilot_chat.rs b/crates/copilot/src/copilot_chat.rs index 4c91b4fedb..e8e2251648 100644 --- a/crates/copilot/src/copilot_chat.rs +++ b/crates/copilot/src/copilot_chat.rs @@ -484,7 +484,7 @@ impl CopilotChat { }; if this.oauth_token.is_some() { - cx.spawn(async move |this, mut cx| Self::update_models(&this, &mut cx).await) + cx.spawn(async move |this, cx| Self::update_models(&this, cx).await) .detach_and_log_err(cx); } @@ -863,7 +863,7 @@ mod tests { "object": "list" }"#; - let schema: ModelSchema = serde_json::from_str(&json).unwrap(); + let schema: ModelSchema = serde_json::from_str(json).unwrap(); assert_eq!(schema.data.len(), 2); assert_eq!(schema.data[0].id, "gpt-4"); diff --git a/crates/crashes/Cargo.toml b/crates/crashes/Cargo.toml index 2420b499f8..f12913d1cb 100644 --- a/crates/crashes/Cargo.toml +++ b/crates/crashes/Cargo.toml @@ -16,6 +16,9 @@ serde.workspace = true serde_json.workspace = true workspace-hack.workspace = true +[target.'cfg(target_os = "macos")'.dependencies] +mach2.workspace = true + [lints] workspace = true diff --git a/crates/crashes/src/crashes.rs b/crates/crashes/src/crashes.rs index ddf6468be8..4e4b69f639 100644 --- a/crates/crashes/src/crashes.rs +++ b/crates/crashes/src/crashes.rs @@ -74,6 +74,9 @@ pub async fn init(crash_init: InitCrashHandler) { .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) .is_ok() { + #[cfg(target_os = "macos")] + suspend_all_other_threads(); + client.ping().unwrap(); client.request_dump(crash_context).is_ok() } else { @@ -98,6 +101,23 @@ pub async fn init(crash_init: InitCrashHandler) { } } +#[cfg(target_os = "macos")] +unsafe fn suspend_all_other_threads() { + let task = unsafe { mach2::traps::current_task() }; + let mut threads: mach2::mach_types::thread_act_array_t = std::ptr::null_mut(); + let mut count = 0; + unsafe { + mach2::task::task_threads(task, &raw mut threads, &raw mut count); + } + let current = unsafe { mach2::mach_init::mach_thread_self() }; + for i in 0..count { + let t = unsafe { *threads.add(i as usize) }; + if t != current { + unsafe { mach2::thread_act::thread_suspend(t) }; + } + } +} + pub struct CrashServer { initialization_params: OnceLock, panic_info: OnceLock, @@ -108,6 +128,7 @@ pub struct CrashServer { pub struct CrashInfo { pub init: InitCrashHandler, pub panic: Option, + pub minidump_error: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -142,16 +163,14 @@ impl minidumper::ServerHandler for CrashServer { } fn on_minidump_created(&self, result: Result) -> LoopAction { - match result { + let minidump_error = match result { Ok(mut md_bin) => { use io::Write; let _ = md_bin.file.flush(); - info!("wrote minidump to disk {:?}", md_bin.path); + None } - Err(e) => { - info!("failed to write minidump: {:#}", e); - } - } + Err(e) => Some(format!("{e:?}")), + }; let crash_info = CrashInfo { init: self @@ -160,6 +179,7 @@ impl minidumper::ServerHandler for CrashServer { .expect("not initialized") .clone(), panic: self.panic_info.get().cloned(), + minidump_error, }; let crash_data_path = paths::logs_dir() diff --git a/crates/credentials_provider/src/credentials_provider.rs b/crates/credentials_provider/src/credentials_provider.rs index f72fd6c39b..2c8dd6fc81 100644 --- a/crates/credentials_provider/src/credentials_provider.rs +++ b/crates/credentials_provider/src/credentials_provider.rs @@ -19,7 +19,7 @@ use release_channel::ReleaseChannel; /// Only works in development. Setting this environment variable in other /// release channels is a no-op. static ZED_DEVELOPMENT_USE_KEYCHAIN: LazyLock = LazyLock::new(|| { - std::env::var("ZED_DEVELOPMENT_USE_KEYCHAIN").map_or(false, |value| !value.is_empty()) + std::env::var("ZED_DEVELOPMENT_USE_KEYCHAIN").is_ok_and(|value| !value.is_empty()) }); /// A provider for credentials. diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 687305ae94..2cef266677 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -285,7 +285,7 @@ pub async fn download_adapter_from_github( } if !adapter_path.exists() { - fs.create_dir(&adapter_path.as_path()) + fs.create_dir(adapter_path.as_path()) .await .context("Failed creating adapter path")?; } diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index 842bb264a8..25dc875740 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -385,7 +385,7 @@ impl DebugAdapter for CodeLldbDebugAdapter { && let Some(source_languages) = config.get("sourceLanguages").filter(|value| { value .as_array() - .map_or(false, |array| array.iter().all(Value::is_string)) + .is_some_and(|array| array.iter().all(Value::is_string)) }) { let ret = vec![ diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index 22d8262b93..db8a45ceb4 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -36,7 +36,7 @@ impl GoDebugAdapter { delegate: &Arc, ) -> Result { let release = latest_github_release( - &"zed-industries/delve-shim-dap", + "zed-industries/delve-shim-dap", true, false, delegate.http_client(), diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 2d19921a0f..a8826d563b 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -99,10 +99,10 @@ impl JsDebugAdapter { } } - if let Some(env) = configuration.get("env").cloned() { - if let Ok(env) = serde_json::from_value(env) { - envs = env; - } + if let Some(env) = configuration.get("env").cloned() + && let Ok(env) = serde_json::from_value(env) + { + envs = env; } configuration @@ -514,7 +514,7 @@ impl DebugAdapter for JsDebugAdapter { } } - self.get_installed_binary(delegate, &config, user_installed_path, user_args, cx) + self.get_installed_binary(delegate, config, user_installed_path, user_args, cx) .await } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index a2bd934311..614cd0e05d 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -24,6 +24,7 @@ use util::{ResultExt, maybe}; #[derive(Default)] pub(crate) struct PythonDebugAdapter { + base_venv_path: OnceCell, String>>, debugpy_whl_base_path: OnceCell, String>>, } @@ -91,14 +92,12 @@ impl PythonDebugAdapter { }) } - async fn fetch_wheel(delegate: &Arc) -> Result, String> { - let system_python = Self::system_python_name(delegate) - .await - .ok_or_else(|| String::from("Could not find a Python installation"))?; - let command: &OsStr = system_python.as_ref(); + async fn fetch_wheel(&self, delegate: &Arc) -> Result, String> { let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME).join("wheels"); std::fs::create_dir_all(&download_dir).map_err(|e| e.to_string())?; - let installation_succeeded = util::command::new_smol_command(command) + let system_python = self.base_venv_path(delegate).await?; + + let installation_succeeded = util::command::new_smol_command(system_python.as_ref()) .args([ "-m", "pip", @@ -114,7 +113,7 @@ impl PythonDebugAdapter { .status .success(); if !installation_succeeded { - return Err("debugpy installation failed".into()); + return Err("debugpy installation failed (could not fetch Debugpy's wheel)".into()); } let wheel_path = std::fs::read_dir(&download_dir) @@ -139,7 +138,7 @@ impl PythonDebugAdapter { Ok(Arc::from(wheel_path.path())) } - async fn maybe_fetch_new_wheel(delegate: &Arc) { + async fn maybe_fetch_new_wheel(&self, delegate: &Arc) { let latest_release = delegate .http_client() .get( @@ -191,7 +190,7 @@ impl PythonDebugAdapter { ) .await .ok()?; - Self::fetch_wheel(delegate).await.ok()?; + self.fetch_wheel(delegate).await.ok()?; } Some(()) }) @@ -204,7 +203,7 @@ impl PythonDebugAdapter { ) -> Result, String> { self.debugpy_whl_base_path .get_or_init(|| async move { - Self::maybe_fetch_new_wheel(delegate).await; + self.maybe_fetch_new_wheel(delegate).await; Ok(Arc::from( debug_adapters_dir() .join(Self::ADAPTER_NAME) @@ -217,6 +216,45 @@ impl PythonDebugAdapter { .clone() } + async fn base_venv_path(&self, delegate: &Arc) -> Result, String> { + self.base_venv_path + .get_or_init(|| async { + let base_python = Self::system_python_name(delegate) + .await + .ok_or_else(|| String::from("Could not find a Python installation"))?; + + let did_succeed = util::command::new_smol_command(base_python) + .args(["-m", "venv", "zed_base_venv"]) + .current_dir( + paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref()), + ) + .spawn() + .map_err(|e| format!("{e:#?}"))? + .status() + .await + .map_err(|e| format!("{e:#?}"))? + .success(); + if !did_succeed { + return Err("Failed to create base virtual environment".into()); + } + + const DIR: &str = if cfg!(target_os = "windows") { + "Scripts" + } else { + "bin" + }; + Ok(Arc::from( + paths::debug_adapters_dir() + .join(Self::DEBUG_ADAPTER_NAME.as_ref()) + .join("zed_base_venv") + .join(DIR) + .join("python3") + .as_ref(), + )) + }) + .await + .clone() + } async fn system_python_name(delegate: &Arc) -> Option { const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"]; let mut name = None; @@ -679,7 +717,7 @@ impl DebugAdapter for PythonDebugAdapter { local_path.display() ); return self - .get_installed_binary(delegate, &config, Some(local_path.clone()), user_args, None) + .get_installed_binary(delegate, config, Some(local_path.clone()), user_args, None) .await; } @@ -716,7 +754,7 @@ impl DebugAdapter for PythonDebugAdapter { return self .get_installed_binary( delegate, - &config, + config, None, user_args, Some(toolchain.path.to_string()), @@ -724,7 +762,7 @@ impl DebugAdapter for PythonDebugAdapter { .await; } - self.get_installed_binary(delegate, &config, None, user_args, None) + self.get_installed_binary(delegate, config, None, user_args, None) .await } diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index de55212cba..8b790cbec8 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -37,7 +37,7 @@ const FALLBACK_DB_NAME: &str = "FALLBACK_MEMORY_DB"; const DB_FILE_NAME: &str = "db.sqlite"; pub static ZED_STATELESS: LazyLock = - LazyLock::new(|| env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty())); + LazyLock::new(|| env::var("ZED_STATELESS").is_ok_and(|v| !v.is_empty())); pub static ALL_FILE_DB_FAILED: LazyLock = LazyLock::new(|| AtomicBool::new(false)); @@ -74,7 +74,7 @@ pub async fn open_db(db_dir: &Path, scope: &str) -> Threa } async fn open_main_db(db_path: &Path) -> Option { - log::info!("Opening database {}", db_path.display()); + log::trace!("Opening database {}", db_path.display()); ThreadSafeConnection::builder::(db_path.to_string_lossy().as_ref(), true) .with_db_initialization_query(DB_INITIALIZE_QUERY) .with_connection_initialize_query(CONNECTION_INITIALIZE_QUERY) @@ -238,7 +238,7 @@ mod tests { .unwrap(); let _bad_db = open_db::( tempdir.path(), - &release_channel::ReleaseChannel::Dev.dev_name(), + release_channel::ReleaseChannel::Dev.dev_name(), ) .await; } @@ -279,7 +279,7 @@ mod tests { { let corrupt_db = open_db::( tempdir.path(), - &release_channel::ReleaseChannel::Dev.dev_name(), + release_channel::ReleaseChannel::Dev.dev_name(), ) .await; assert!(corrupt_db.persistent()); @@ -287,7 +287,7 @@ mod tests { let good_db = open_db::( tempdir.path(), - &release_channel::ReleaseChannel::Dev.dev_name(), + release_channel::ReleaseChannel::Dev.dev_name(), ) .await; assert!( @@ -334,7 +334,7 @@ mod tests { // Setup the bad database let corrupt_db = open_db::( tempdir.path(), - &release_channel::ReleaseChannel::Dev.dev_name(), + release_channel::ReleaseChannel::Dev.dev_name(), ) .await; assert!(corrupt_db.persistent()); @@ -347,7 +347,7 @@ mod tests { let guard = thread::spawn(move || { let good_db = smol::block_on(open_db::( tmp_path.as_path(), - &release_channel::ReleaseChannel::Dev.dev_name(), + release_channel::ReleaseChannel::Dev.dev_name(), )); assert!( good_db.select_row::("SELECT * FROM test2").unwrap()() diff --git a/crates/db/src/kvp.rs b/crates/db/src/kvp.rs index daf0b136fd..256b789c9b 100644 --- a/crates/db/src/kvp.rs +++ b/crates/db/src/kvp.rs @@ -20,7 +20,7 @@ pub trait Dismissable { KEY_VALUE_STORE .read_kvp(Self::KEY) .log_err() - .map_or(false, |s| s.is_some()) + .is_some_and(|s| s.is_some()) } fn set_dismissed(is_dismissed: bool, cx: &mut App) { diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index b806381d25..131272da6b 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -392,7 +392,7 @@ impl LogStore { session.label(), session .adapter_client() - .map_or(false, |client| client.has_adapter_logs()), + .is_some_and(|client| client.has_adapter_logs()), ) }); @@ -485,7 +485,7 @@ impl LogStore { &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&Vec> { - self.get_debug_adapter_state(&id) + self.get_debug_adapter_state(id) .map(|state| &state.rpc_messages.initialization_sequence) } } @@ -536,11 +536,11 @@ impl Render for DapLogToolbarItemView { }) .unwrap_or_else(|| "No adapter selected".into()), )) - .menu(move |mut window, cx| { + .menu(move |window, cx| { let log_view = log_view.clone(); let menu_rows = menu_rows.clone(); let project = project.clone(); - ContextMenu::build(&mut window, cx, move |mut menu, window, _cx| { + ContextMenu::build(window, cx, move |mut menu, window, _cx| { for row in menu_rows.into_iter() { menu = menu.custom_row(move |_window, _cx| { div() @@ -661,11 +661,11 @@ impl ToolbarItemView for DapLogToolbarItemView { _window: &mut Window, cx: &mut Context, ) -> workspace::ToolbarItemLocation { - if let Some(item) = active_pane_item { - if let Some(log_view) = item.downcast::() { - self.log_view = Some(log_view.clone()); - return workspace::ToolbarItemLocation::PrimaryLeft; - } + if let Some(item) = active_pane_item + && let Some(log_view) = item.downcast::() + { + self.log_view = Some(log_view.clone()); + return workspace::ToolbarItemLocation::PrimaryLeft; } self.log_view = None; @@ -1131,7 +1131,7 @@ impl LogStore { project: &WeakEntity, session_id: SessionId, ) -> Vec { - self.projects.get(&project).map_or(vec![], |state| { + self.projects.get(project).map_or(vec![], |state| { state .debug_sessions .get(&session_id) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 1d44c5c244..6c70a935e0 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -257,7 +257,7 @@ impl DebugPanel { .as_ref() .map(|entity| entity.downgrade()), task_context: task_context.clone(), - worktree_id: worktree_id, + worktree_id, }); }; running.resolve_scenario( @@ -530,10 +530,9 @@ impl DebugPanel { .active_session .as_ref() .map(|session| session.entity_id()) + && active_session_id == entity_id { - if active_session_id == entity_id { - this.active_session = this.sessions_with_children.keys().next().cloned(); - } + this.active_session = this.sessions_with_children.keys().next().cloned(); } cx.notify() }) @@ -693,7 +692,7 @@ impl DebugPanel { ) .icon_size(IconSize::Small) .on_click(window.listener_for( - &running_state, + running_state, |this, _, _window, cx| { this.pause_thread(cx); }, @@ -719,7 +718,7 @@ impl DebugPanel { ) .icon_size(IconSize::Small) .on_click(window.listener_for( - &running_state, + running_state, |this, _, _window, cx| this.continue_thread(cx), )) .disabled(thread_status != ThreadStatus::Stopped) @@ -742,7 +741,7 @@ impl DebugPanel { IconButton::new("debug-step-over", IconName::ArrowRight) .icon_size(IconSize::Small) .on_click(window.listener_for( - &running_state, + running_state, |this, _, _window, cx| { this.step_over(cx); }, @@ -768,7 +767,7 @@ impl DebugPanel { ) .icon_size(IconSize::Small) .on_click(window.listener_for( - &running_state, + running_state, |this, _, _window, cx| { this.step_in(cx); }, @@ -791,7 +790,7 @@ impl DebugPanel { IconButton::new("debug-step-out", IconName::ArrowUpRight) .icon_size(IconSize::Small) .on_click(window.listener_for( - &running_state, + running_state, |this, _, _window, cx| { this.step_out(cx); }, @@ -815,7 +814,7 @@ impl DebugPanel { IconButton::new("debug-restart", IconName::RotateCcw) .icon_size(IconSize::Small) .on_click(window.listener_for( - &running_state, + running_state, |this, _, window, cx| { this.rerun_session(window, cx); }, @@ -837,7 +836,7 @@ impl DebugPanel { IconButton::new("debug-stop", IconName::Power) .icon_size(IconSize::Small) .on_click(window.listener_for( - &running_state, + running_state, |this, _, _window, cx| { if this.session().read(cx).is_building() { this.session().update(cx, |session, cx| { @@ -892,7 +891,7 @@ impl DebugPanel { ) .icon_size(IconSize::Small) .on_click(window.listener_for( - &running_state, + running_state, |this, _, _, cx| { this.detach_client(cx); }, @@ -1160,7 +1159,7 @@ impl DebugPanel { workspace .project() .read(cx) - .project_path_for_absolute_path(&path, cx) + .project_path_for_absolute_path(path, cx) .context( "Couldn't get project path for .zed/debug.json in active worktree", ) @@ -1302,10 +1301,10 @@ impl DebugPanel { cx: &mut Context<'_, Self>, ) -> Option { let adapter = parent_session.read(cx).adapter(); - if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) { - if let Some(label) = adapter.label_for_child_session(request) { - return Some(label.into()); - } + if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) + && let Some(label) = adapter.label_for_child_session(request) + { + return Some(label.into()); } None } diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index 4ac8e371a1..eb0ad92dcc 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -343,10 +343,10 @@ impl NewProcessModal { return; } - if let NewProcessMode::Launch = &self.mode { - if self.configure_mode.read(cx).save_to_debug_json.selected() { - self.save_debug_scenario(window, cx); - } + if let NewProcessMode::Launch = &self.mode + && self.configure_mode.read(cx).save_to_debug_json.selected() + { + self.save_debug_scenario(window, cx); } let Some(debugger) = self.debugger.clone() else { @@ -413,7 +413,7 @@ impl NewProcessModal { let Some(adapter) = self.debugger.as_ref() else { return; }; - let scenario = self.debug_scenario(&adapter, cx); + let scenario = self.debug_scenario(adapter, cx); cx.spawn_in(window, async move |this, cx| { let scenario = scenario.await.context("no scenario to save")?; let worktree_id = task_contexts @@ -659,12 +659,7 @@ impl Render for NewProcessModal { this.mode = NewProcessMode::Attach; if let Some(debugger) = this.debugger.as_ref() { - Self::update_attach_picker( - &this.attach_mode, - &debugger, - window, - cx, - ); + Self::update_attach_picker(&this.attach_mode, debugger, window, cx); } this.mode_focus_handle(cx).focus(window); cx.notify(); @@ -1083,7 +1078,7 @@ impl DebugDelegate { .into_iter() .map(|(scenario, context)| { let (kind, scenario) = - Self::get_scenario_kind(&languages, &dap_registry, scenario); + Self::get_scenario_kind(&languages, dap_registry, scenario); (kind, scenario, Some(context)) }) .chain( @@ -1100,7 +1095,7 @@ impl DebugDelegate { .filter(|(_, scenario)| valid_adapters.contains(&scenario.adapter)) .map(|(kind, scenario)| { let (language, scenario) = - Self::get_scenario_kind(&languages, &dap_registry, scenario); + Self::get_scenario_kind(&languages, dap_registry, scenario); (language.or(Some(kind)), scenario, None) }), ) diff --git a/crates/debugger_ui/src/persistence.rs b/crates/debugger_ui/src/persistence.rs index 3a0ad7a40e..f0d7fd6fdd 100644 --- a/crates/debugger_ui/src/persistence.rs +++ b/crates/debugger_ui/src/persistence.rs @@ -341,7 +341,7 @@ impl SerializedPaneLayout { pub(crate) fn in_order(&self) -> Vec { let mut panes = vec![]; - Self::inner_in_order(&self, &mut panes); + Self::inner_in_order(self, &mut panes); panes } diff --git a/crates/debugger_ui/src/session.rs b/crates/debugger_ui/src/session.rs index 73cfef78cc..0fc003a14d 100644 --- a/crates/debugger_ui/src/session.rs +++ b/crates/debugger_ui/src/session.rs @@ -87,7 +87,7 @@ impl DebugSession { self.stack_trace_view.get_or_init(|| { let stackframe_list = running_state.read(cx).stack_frame_list().clone(); - let stack_frame_view = cx.new(|cx| { + cx.new(|cx| { StackTraceView::new( workspace.clone(), project.clone(), @@ -95,9 +95,7 @@ impl DebugSession { window, cx, ) - }); - - stack_frame_view + }) }) } diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index f3117aee07..e3682ac991 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -102,7 +102,7 @@ impl Render for RunningState { .find(|pane| pane.read(cx).is_zoomed()); let active = self.panes.panes().into_iter().next(); - let pane = if let Some(ref zoomed_pane) = zoomed_pane { + let pane = if let Some(zoomed_pane) = zoomed_pane { zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element()) } else if let Some(active) = active { self.panes @@ -358,7 +358,7 @@ pub(crate) fn new_debugger_pane( } }; - let ret = cx.new(move |cx| { + cx.new(move |cx| { let mut pane = Pane::new( workspace.clone(), project.clone(), @@ -414,7 +414,7 @@ pub(crate) fn new_debugger_pane( .and_then(|item| item.downcast::()); let is_hovered = as_subview .as_ref() - .map_or(false, |item| item.read(cx).hovered); + .is_some_and(|item| item.read(cx).hovered); h_flex() .track_focus(&focus_handle) @@ -427,7 +427,6 @@ pub(crate) fn new_debugger_pane( .bg(cx.theme().colors().tab_bar_background) .on_action(|_: &menu::Cancel, window, cx| { if cx.stop_active_drag(window) { - return; } else { cx.propagate(); } @@ -449,7 +448,7 @@ pub(crate) fn new_debugger_pane( .children(pane.items().enumerate().map(|(ix, item)| { let selected = active_pane_item .as_ref() - .map_or(false, |active| active.item_id() == item.item_id()); + .is_some_and(|active| active.item_id() == item.item_id()); let deemphasized = !pane.has_focus(window, cx); let item_ = item.boxed_clone(); div() @@ -563,9 +562,7 @@ pub(crate) fn new_debugger_pane( } }); pane - }); - - ret + }) } pub struct DebugTerminal { @@ -627,7 +624,7 @@ impl RunningState { if s.starts_with("\"$ZED_") && s.ends_with('"') { *s = s[1..s.len() - 1].to_string(); } - if let Some(substituted) = substitute_variables_in_str(&s, context) { + if let Some(substituted) = substitute_variables_in_str(s, context) { *s = substituted; } } @@ -657,7 +654,7 @@ impl RunningState { } resolve_path(s); - if let Some(substituted) = substitute_variables_in_str(&s, context) { + if let Some(substituted) = substitute_variables_in_str(s, context) { *s = substituted; } } @@ -954,7 +951,7 @@ impl RunningState { inventory.read(cx).task_template_by_label( buffer, worktree_id, - &label, + label, cx, ) }) @@ -1310,7 +1307,7 @@ impl RunningState { let mut pane_item_status = IndexMap::from_iter( DebuggerPaneItem::all() .iter() - .filter(|kind| kind.is_supported(&caps)) + .filter(|kind| kind.is_supported(caps)) .map(|kind| (*kind, false)), ); self.panes.panes().iter().for_each(|pane| { @@ -1371,7 +1368,7 @@ impl RunningState { this.serialize_layout(window, cx); match event { Event::Remove { .. } => { - let _did_find_pane = this.panes.remove(&source_pane).is_ok(); + let _did_find_pane = this.panes.remove(source_pane).is_ok(); debug_assert!(_did_find_pane); cx.notify(); } diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index 5d85f08bdb..0ba5b90aa1 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -236,11 +236,9 @@ impl BreakpointList { } fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context) { - if self.strip_mode.is_some() { - if self.input.focus_handle(cx).contains_focused(window, cx) { - cx.propagate(); - return; - } + if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; } let ix = match self.selected_ix { _ if self.breakpoints.len() == 0 => None, @@ -262,11 +260,9 @@ impl BreakpointList { window: &mut Window, cx: &mut Context, ) { - if self.strip_mode.is_some() { - if self.input.focus_handle(cx).contains_focused(window, cx) { - cx.propagate(); - return; - } + if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; } let ix = match self.selected_ix { _ if self.breakpoints.len() == 0 => None, @@ -283,11 +279,9 @@ impl BreakpointList { } fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context) { - if self.strip_mode.is_some() { - if self.input.focus_handle(cx).contains_focused(window, cx) { - cx.propagate(); - return; - } + if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; } let ix = if self.breakpoints.len() > 0 { Some(0) @@ -298,11 +292,9 @@ impl BreakpointList { } fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context) { - if self.strip_mode.is_some() { - if self.input.focus_handle(cx).contains_focused(window, cx) { - cx.propagate(); - return; - } + if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; } let ix = if self.breakpoints.len() > 0 { Some(self.breakpoints.len() - 1) @@ -334,8 +326,8 @@ impl BreakpointList { let text = self.input.read(cx).text(cx); match mode { - ActiveBreakpointStripMode::Log => match &entry.kind { - BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { + ActiveBreakpointStripMode::Log => { + if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind { Self::edit_line_breakpoint_inner( &self.breakpoint_store, line_breakpoint.breakpoint.path.clone(), @@ -344,10 +336,9 @@ impl BreakpointList { cx, ); } - _ => {} - }, - ActiveBreakpointStripMode::Condition => match &entry.kind { - BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { + } + ActiveBreakpointStripMode::Condition => { + if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind { Self::edit_line_breakpoint_inner( &self.breakpoint_store, line_breakpoint.breakpoint.path.clone(), @@ -356,10 +347,9 @@ impl BreakpointList { cx, ); } - _ => {} - }, - ActiveBreakpointStripMode::HitCondition => match &entry.kind { - BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { + } + ActiveBreakpointStripMode::HitCondition => { + if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind { Self::edit_line_breakpoint_inner( &self.breakpoint_store, line_breakpoint.breakpoint.path.clone(), @@ -368,8 +358,7 @@ impl BreakpointList { cx, ); } - _ => {} - }, + } } self.focus_handle.focus(window); } else { @@ -398,11 +387,9 @@ impl BreakpointList { let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else { return; }; - if self.strip_mode.is_some() { - if self.input.focus_handle(cx).contains_focused(window, cx) { - cx.propagate(); - return; - } + if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; } match &mut entry.kind { @@ -433,13 +420,10 @@ impl BreakpointList { return; }; - match &mut entry.kind { - BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { - let path = line_breakpoint.breakpoint.path.clone(); - let row = line_breakpoint.breakpoint.row; - self.edit_line_breakpoint(path, row, BreakpointEditAction::Toggle, cx); - } - _ => {} + if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &mut entry.kind { + let path = line_breakpoint.breakpoint.path.clone(); + let row = line_breakpoint.breakpoint.row; + self.edit_line_breakpoint(path, row, BreakpointEditAction::Toggle, cx); } cx.notify(); } @@ -491,7 +475,7 @@ impl BreakpointList { fn toggle_data_breakpoint(&mut self, id: &str, cx: &mut Context) { if let Some(session) = &self.session { session.update(cx, |this, cx| { - this.toggle_data_breakpoint(&id, cx); + this.toggle_data_breakpoint(id, cx); }); } } @@ -499,7 +483,7 @@ impl BreakpointList { fn toggle_exception_breakpoint(&mut self, id: &str, cx: &mut Context) { if let Some(session) = &self.session { session.update(cx, |this, cx| { - this.toggle_exception_breakpoint(&id, cx); + this.toggle_exception_breakpoint(id, cx); }); cx.notify(); const EXCEPTION_SERIALIZATION_INTERVAL: Duration = Duration::from_secs(1); @@ -535,7 +519,7 @@ impl BreakpointList { cx.background_executor() .spawn(async move { KEY_VALUE_STORE.write_kvp(key, value?).await }) } else { - return Task::ready(Result::Ok(())); + Task::ready(Result::Ok(())) } } @@ -941,7 +925,7 @@ impl LineBreakpoint { props, breakpoint: BreakpointEntry { kind: BreakpointEntryKind::LineBreakpoint(self.clone()), - weak: weak, + weak, }, is_selected, focus_handle, @@ -1153,7 +1137,7 @@ impl ExceptionBreakpoint { props, breakpoint: BreakpointEntry { kind: BreakpointEntryKind::ExceptionBreakpoint(self.clone()), - weak: weak, + weak, }, is_selected, focus_handle, diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index e6308518e4..05d2231da4 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -611,17 +611,16 @@ impl ConsoleQueryBarCompletionProvider { for variable in console.variable_list.update(cx, |variable_list, cx| { variable_list.completion_variables(cx) }) { - if let Some(evaluate_name) = &variable.evaluate_name { - if variables + if let Some(evaluate_name) = &variable.evaluate_name + && variables .insert(evaluate_name.clone(), variable.value.clone()) .is_none() - { - string_matches.push(StringMatchCandidate { - id: 0, - string: evaluate_name.clone(), - char_bag: evaluate_name.chars().collect(), - }); - } + { + string_matches.push(StringMatchCandidate { + id: 0, + string: evaluate_name.clone(), + char_bag: evaluate_name.chars().collect(), + }); } if variables @@ -697,7 +696,7 @@ impl ConsoleQueryBarCompletionProvider { new_bytes: &[u8], snapshot: &TextBufferSnapshot, ) -> Range { - let buffer_offset = buffer_position.to_offset(&snapshot); + let buffer_offset = buffer_position.to_offset(snapshot); let buffer_bytes = &buffer_text.as_bytes()[0..buffer_offset]; let mut prefix_len = 0; @@ -977,7 +976,7 @@ mod tests { &cx.buffer_text(), snapshot.anchor_before(buffer_position), replacement.as_bytes(), - &snapshot, + snapshot, ); cx.update_editor(|editor, _, cx| { diff --git a/crates/debugger_ui/src/session/running/memory_view.rs b/crates/debugger_ui/src/session/running/memory_view.rs index e719c896bd..9b77ec75be 100644 --- a/crates/debugger_ui/src/session/running/memory_view.rs +++ b/crates/debugger_ui/src/session/running/memory_view.rs @@ -221,7 +221,7 @@ impl MemoryView { cx: &mut Context, ) { use parse_int::parse; - let Ok(as_address) = parse::(&memory_reference) else { + let Ok(as_address) = parse::(memory_reference) else { return; }; let access_size = evaluate_name @@ -892,7 +892,7 @@ impl Render for MemoryView { v_flex() .size_full() .on_drag_move(cx.listener(|this, evt, _, _| { - this.handle_memory_drag(&evt); + this.handle_memory_drag(evt); })) .child(self.render_memory(cx).size_full()) .children(self.open_context_menu.as_ref().map(|(menu, position, _)| { diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs index 1a506312b8..f312032450 100644 --- a/crates/debugger_ui/src/session/running/variable_list.rs +++ b/crates/debugger_ui/src/session/running/variable_list.rs @@ -269,7 +269,7 @@ impl VariableList { let mut entries = vec![]; let scopes: Vec<_> = self.session.update(cx, |session, cx| { - session.scopes(stack_frame_id, cx).iter().cloned().collect() + session.scopes(stack_frame_id, cx).to_vec() }); let mut contains_local_scope = false; @@ -944,7 +944,7 @@ impl VariableList { #[track_caller] #[cfg(test)] pub(crate) fn assert_visual_entries(&self, expected: Vec<&str>) { - const INDENT: &'static str = " "; + const INDENT: &str = " "; let entries = &self.entries; let mut visual_entries = Vec::with_capacity(entries.len()); @@ -1286,7 +1286,7 @@ impl VariableList { }), ) .child(self.render_variable_value( - &entry, + entry, &variable_color, watcher.value.to_string(), cx, @@ -1491,7 +1491,7 @@ impl VariableList { }), ) .child(self.render_variable_value( - &variable, + variable, &variable_color, dap.value.clone(), cx, diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index 906a7a0d4b..80e2b73d5a 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/crates/debugger_ui/src/tests/attach_modal.rs @@ -139,7 +139,7 @@ async fn test_show_attach_modal_and_select_process( workspace .update(cx, |_, window, cx| { let names = - attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx)); + attach_modal.update(cx, |modal, cx| attach_modal::_process_names(modal, cx)); // Initially all processes are visible. assert_eq!(3, names.len()); attach_modal.update(cx, |this, cx| { @@ -154,7 +154,7 @@ async fn test_show_attach_modal_and_select_process( workspace .update(cx, |_, _, cx| { let names = - attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx)); + attach_modal.update(cx, |modal, cx| attach_modal::_process_names(modal, cx)); // Initially all processes are visible. assert_eq!(2, names.len()); }) diff --git a/crates/debugger_ui/src/tests/new_process_modal.rs b/crates/debugger_ui/src/tests/new_process_modal.rs index d6b0dfa004..5ac6af389d 100644 --- a/crates/debugger_ui/src/tests/new_process_modal.rs +++ b/crates/debugger_ui/src/tests/new_process_modal.rs @@ -107,7 +107,7 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths( let expected_other_field = if input_path.contains("$ZED_WORKTREE_ROOT") { input_path - .replace("$ZED_WORKTREE_ROOT", &path!("/test/worktree/path")) + .replace("$ZED_WORKTREE_ROOT", path!("/test/worktree/path")) .to_owned() } else { input_path.to_string() diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index fbbd529641..4cfdae093f 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -1445,11 +1445,8 @@ async fn test_variable_list_only_sends_requests_when_rendering( cx.run_until_parked(); - let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, _| { - let state = item.running_state().clone(); - - state - }); + let running_state = active_debug_session_panel(workspace, cx) + .update_in(cx, |item, _, _| item.running_state().clone()); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { diff --git a/crates/diagnostics/src/diagnostic_renderer.rs b/crates/diagnostics/src/diagnostic_renderer.rs index ce7b253702..e9731f84ce 100644 --- a/crates/diagnostics/src/diagnostic_renderer.rs +++ b/crates/diagnostics/src/diagnostic_renderer.rs @@ -46,7 +46,7 @@ impl DiagnosticRenderer { markdown.push_str(" ("); } if let Some(source) = diagnostic.source.as_ref() { - markdown.push_str(&Markdown::escape(&source)); + markdown.push_str(&Markdown::escape(source)); } if diagnostic.source.is_some() && diagnostic.code.is_some() { markdown.push(' '); @@ -287,15 +287,13 @@ impl DiagnosticBlock { } } } - } else { - if let Some(diagnostic) = editor - .snapshot(window, cx) - .buffer_snapshot - .diagnostic_group(buffer_id, group_id) - .nth(ix) - { - Self::jump_to(editor, diagnostic.range, window, cx) - } + } else if let Some(diagnostic) = editor + .snapshot(window, cx) + .buffer_snapshot + .diagnostic_group(buffer_id, group_id) + .nth(ix) + { + Self::jump_to(editor, diagnostic.range, window, cx) }; } @@ -306,7 +304,7 @@ impl DiagnosticBlock { cx: &mut Context, ) { let snapshot = &editor.buffer().read(cx).snapshot(cx); - let range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot); + let range = range.start.to_offset(snapshot)..range.end.to_offset(snapshot); editor.unfold_ranges(&[range.start..range.end], true, false, cx); editor.change_selections(Default::default(), window, cx, |s| { diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e7660920da..2e20118381 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -383,12 +383,10 @@ impl ProjectDiagnosticsEditor { } else { self.update_all_diagnostics(false, window, cx); } + } else if self.update_excerpts_task.is_some() { + self.update_excerpts_task = None; } else { - if self.update_excerpts_task.is_some() { - self.update_excerpts_task = None; - } else { - self.update_all_diagnostics(false, window, cx); - } + self.update_all_diagnostics(false, window, cx); } cx.notify(); } @@ -528,7 +526,7 @@ impl ProjectDiagnosticsEditor { lsp::DiagnosticSeverity::ERROR }; - cx.spawn_in(window, async move |this, mut cx| { + cx.spawn_in(window, async move |this, cx| { let diagnostics = buffer_snapshot .diagnostics_in_range::<_, text::Anchor>( Point::zero()..buffer_snapshot.max_point(), @@ -542,7 +540,7 @@ impl ProjectDiagnosticsEditor { return true; } this.diagnostics.insert(buffer_id, diagnostics.clone()); - return false; + false })?; if unchanged { return Ok(()); @@ -595,7 +593,7 @@ impl ProjectDiagnosticsEditor { b.initial_range.clone(), DEFAULT_MULTIBUFFER_CONTEXT, buffer_snapshot.clone(), - &mut cx, + cx, ) .await; let i = excerpt_ranges @@ -639,17 +637,15 @@ impl ProjectDiagnosticsEditor { #[cfg(test)] let cloned_blocks = blocks.clone(); - if was_empty { - if let Some(anchor_range) = anchor_ranges.first() { - let range_to_select = anchor_range.start..anchor_range.start; - this.editor.update(cx, |editor, cx| { - editor.change_selections(Default::default(), window, cx, |s| { - s.select_anchor_ranges([range_to_select]); - }) - }); - if this.focus_handle.is_focused(window) { - this.editor.read(cx).focus_handle(cx).focus(window); - } + if was_empty && let Some(anchor_range) = anchor_ranges.first() { + let range_to_select = anchor_range.start..anchor_range.start; + this.editor.update(cx, |editor, cx| { + editor.change_selections(Default::default(), window, cx, |s| { + s.select_anchor_ranges([range_to_select]); + }) + }); + if this.focus_handle.is_focused(window) { + this.editor.read(cx).focus_handle(cx).focus(window); } } @@ -980,18 +976,16 @@ async fn heuristic_syntactic_expand( // Remove blank lines from start and end if let Some(start_row) = (outline_range.start.row..outline_range.end.row) .find(|row| !snapshot.line_indent_for_row(*row).is_line_blank()) - { - if let Some(end_row) = (outline_range.start.row..outline_range.end.row + 1) + && let Some(end_row) = (outline_range.start.row..outline_range.end.row + 1) .rev() .find(|row| !snapshot.line_indent_for_row(*row).is_line_blank()) - { - let row_count = end_row.saturating_sub(start_row); - if row_count <= max_row_count { - return Some(RangeInclusive::new( - outline_range.start.row, - outline_range.end.row, - )); - } + { + let row_count = end_row.saturating_sub(start_row); + if row_count <= max_row_count { + return Some(RangeInclusive::new( + outline_range.start.row, + outline_range.end.row, + )); } } } diff --git a/crates/docs_preprocessor/src/main.rs b/crates/docs_preprocessor/src/main.rs index 17804b4281..99e588ada9 100644 --- a/crates/docs_preprocessor/src/main.rs +++ b/crates/docs_preprocessor/src/main.rs @@ -21,7 +21,7 @@ static KEYMAP_LINUX: LazyLock = LazyLock::new(|| { static ALL_ACTIONS: LazyLock> = LazyLock::new(dump_all_gpui_actions); -const FRONT_MATTER_COMMENT: &'static str = ""; +const FRONT_MATTER_COMMENT: &str = ""; fn main() -> Result<()> { zlog::init(); @@ -105,8 +105,8 @@ fn handle_preprocessing() -> Result<()> { template_and_validate_actions(&mut book, &mut errors); if !errors.is_empty() { - const ANSI_RED: &'static str = "\x1b[31m"; - const ANSI_RESET: &'static str = "\x1b[0m"; + const ANSI_RED: &str = "\x1b[31m"; + const ANSI_RESET: &str = "\x1b[0m"; for error in &errors { eprintln!("{ANSI_RED}ERROR{ANSI_RESET}: {}", error); } @@ -129,7 +129,7 @@ fn handle_frontmatter(book: &mut Book, errors: &mut HashSet) let Some((name, value)) = line.split_once(':') else { errors.insert(PreprocessorError::InvalidFrontmatterLine(format!( "{}: {}", - chapter_breadcrumbs(&chapter), + chapter_breadcrumbs(chapter), line ))); continue; @@ -143,11 +143,8 @@ fn handle_frontmatter(book: &mut Book, errors: &mut HashSet) &serde_json::to_string(&metadata).expect("Failed to serialize metadata"), ) }); - match new_content { - Cow::Owned(content) => { - chapter.content = content; - } - Cow::Borrowed(_) => {} + if let Cow::Owned(content) = new_content { + chapter.content = content; } }); } @@ -295,7 +292,7 @@ fn dump_all_gpui_actions() -> Vec { actions.sort_by_key(|a| a.name); - return actions; + actions } fn handle_postprocessing() -> Result<()> { @@ -402,20 +399,20 @@ fn handle_postprocessing() -> Result<()> { path: &'a std::path::PathBuf, root: &'a std::path::PathBuf, ) -> &'a std::path::Path { - &path.strip_prefix(&root).unwrap_or(&path) + path.strip_prefix(&root).unwrap_or(path) } fn extract_title_from_page(contents: &str, pretty_path: &std::path::Path) -> String { let title_tag_contents = &title_regex() - .captures(&contents) + .captures(contents) .with_context(|| format!("Failed to find title in {:?}", pretty_path)) .expect("Page has element")[1]; - let title = title_tag_contents + + title_tag_contents .trim() .strip_suffix("- Zed") .unwrap_or(title_tag_contents) .trim() - .to_string(); - title + .to_string() } } diff --git a/crates/edit_prediction_button/src/edit_prediction_button.rs b/crates/edit_prediction_button/src/edit_prediction_button.rs index 4632a03daf..21c934fefa 100644 --- a/crates/edit_prediction_button/src/edit_prediction_button.rs +++ b/crates/edit_prediction_button/src/edit_prediction_button.rs @@ -185,7 +185,7 @@ impl Render for EditPredictionButton { let this = cx.entity(); let fs = self.fs.clone(); - return div().child( + div().child( PopoverMenu::new("supermaven") .menu(move |window, cx| match &status { SupermavenButtonStatus::NeedsActivation(activate_url) => { @@ -230,7 +230,7 @@ impl Render for EditPredictionButton { }, ) .with_handle(self.popover_menu_handle.clone()), - ); + ) } EditPredictionProvider::Zed => { @@ -343,7 +343,7 @@ impl Render for EditPredictionButton { let is_refreshing = self .edit_prediction_provider .as_ref() - .map_or(false, |provider| provider.is_refreshing(cx)); + .is_some_and(|provider| provider.is_refreshing(cx)); if is_refreshing { popover_menu = popover_menu.trigger( diff --git a/crates/editor/src/clangd_ext.rs b/crates/editor/src/clangd_ext.rs index 3239fdc653..c78d4c83c0 100644 --- a/crates/editor/src/clangd_ext.rs +++ b/crates/editor/src/clangd_ext.rs @@ -13,7 +13,7 @@ use crate::{Editor, SwitchSourceHeader, element::register_action}; use project::lsp_store::clangd_ext::CLANGD_SERVER_NAME; fn is_c_language(language: &Language) -> bool { - return language.name() == "C++".into() || language.name() == "C".into(); + language.name() == "C++".into() || language.name() == "C".into() } pub fn switch_source_header( @@ -104,6 +104,6 @@ pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: & .filter_map(|buffer| buffer.read(cx).language()) .any(|language| is_c_language(language)) { - register_action(&editor, window, switch_source_header); + register_action(editor, window, switch_source_header); } } diff --git a/crates/editor/src/code_completion_tests.rs b/crates/editor/src/code_completion_tests.rs index fd8db29584..a1d9f04a9c 100644 --- a/crates/editor/src/code_completion_tests.rs +++ b/crates/editor/src/code_completion_tests.rs @@ -317,7 +317,7 @@ async fn filter_and_sort_matches( let candidates: Arc<[StringMatchCandidate]> = completions .iter() .enumerate() - .map(|(id, completion)| StringMatchCandidate::new(id, &completion.label.filter_text())) + .map(|(id, completion)| StringMatchCandidate::new(id, completion.label.filter_text())) .collect(); let cancel_flag = Arc::new(AtomicBool::new(false)); let background_executor = cx.executor(); @@ -331,5 +331,5 @@ async fn filter_and_sort_matches( background_executor, ) .await; - CompletionsMenu::sort_string_matches(matches, Some(query), snippet_sort_order, &completions) + CompletionsMenu::sort_string_matches(matches, Some(query), snippet_sort_order, completions) } diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 4ae2a14ca7..4847bc2565 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -321,7 +321,7 @@ impl CompletionsMenu { let match_candidates = choices .iter() .enumerate() - .map(|(id, completion)| StringMatchCandidate::new(id, &completion)) + .map(|(id, completion)| StringMatchCandidate::new(id, completion)) .collect(); let entries = choices .iter() @@ -1111,10 +1111,8 @@ impl CompletionsMenu { let query_start_doesnt_match_split_words = query_start_lower .map(|query_char| { !split_words(&string_match.string).any(|word| { - word.chars() - .next() - .and_then(|c| c.to_lowercase().next()) - .map_or(false, |word_char| word_char == query_char) + word.chars().next().and_then(|c| c.to_lowercase().next()) + == Some(query_char) }) }) .unwrap_or(false); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a16e516a70..c16e4a6ddb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -969,13 +969,13 @@ impl DisplaySnapshot { if let Some(chunk_highlight) = chunk.highlight_style { // For color inlays, blend the color with the editor background let mut processed_highlight = chunk_highlight; - if chunk.is_inlay { - if let Some(inlay_color) = chunk_highlight.color { - // Only blend if the color has transparency (alpha < 1.0) - if inlay_color.a < 1.0 { - let blended_color = editor_style.background.blend(inlay_color); - processed_highlight.color = Some(blended_color); - } + if chunk.is_inlay + && let Some(inlay_color) = chunk_highlight.color + { + // Only blend if the color has transparency (alpha < 1.0) + if inlay_color.a < 1.0 { + let blended_color = editor_style.background.blend(inlay_color); + processed_highlight.color = Some(blended_color); } } @@ -991,7 +991,7 @@ impl DisplaySnapshot { if let Some(severity) = chunk.diagnostic_severity.filter(|severity| { self.diagnostics_max_severity .into_lsp() - .map_or(false, |max_severity| severity <= &max_severity) + .is_some_and(|max_severity| severity <= &max_severity) }) { if chunk.is_unnecessary { diagnostic_highlight.fade_out = Some(editor_style.unnecessary_code_fade); @@ -2351,11 +2351,12 @@ pub mod tests { .highlight_style .and_then(|style| style.color) .map_or(black, |color| color.to_rgb()); - if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut() { - if *last_severity == chunk.diagnostic_severity && *last_color == color { - last_chunk.push_str(chunk.text); - continue; - } + if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut() + && *last_severity == chunk.diagnostic_severity + && *last_color == color + { + last_chunk.push_str(chunk.text); + continue; } chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color)); @@ -2901,11 +2902,12 @@ pub mod tests { .syntax_highlight_id .and_then(|id| id.style(theme)?.color); let highlight_color = chunk.highlight_style.and_then(|style| style.color); - if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() { - if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color { - last_chunk.push_str(chunk.text); - continue; - } + if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() + && syntax_color == *last_syntax_color + && highlight_color == *last_highlight_color + { + last_chunk.push_str(chunk.text); + continue; } chunks.push((chunk.text.to_string(), syntax_color, highlight_color)); } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index c4c9f2004a..5d5c9500eb 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -525,26 +525,22 @@ impl BlockMap { // * Below blocks that end at the start of the edit // However, if we hit a replace block that ends at the start of the edit we want to reconstruct it. new_transforms.append(cursor.slice(&old_start, Bias::Left), &()); - if let Some(transform) = cursor.item() { - if transform.summary.input_rows > 0 - && cursor.end() == old_start - && transform - .block - .as_ref() - .map_or(true, |b| !b.is_replacement()) - { - // Preserve the transform (push and next) - new_transforms.push(transform.clone(), &()); - cursor.next(); + if let Some(transform) = cursor.item() + && transform.summary.input_rows > 0 + && cursor.end() == old_start + && transform.block.as_ref().is_none_or(|b| !b.is_replacement()) + { + // Preserve the transform (push and next) + new_transforms.push(transform.clone(), &()); + cursor.next(); - // Preserve below blocks at end of edit - while let Some(transform) = cursor.item() { - if transform.block.as_ref().map_or(false, |b| b.place_below()) { - new_transforms.push(transform.clone(), &()); - cursor.next(); - } else { - break; - } + // Preserve below blocks at end of edit + while let Some(transform) = cursor.item() { + if transform.block.as_ref().is_some_and(|b| b.place_below()) { + new_transforms.push(transform.clone(), &()); + cursor.next(); + } else { + break; } } } @@ -607,7 +603,7 @@ impl BlockMap { // Discard below blocks at the end of the edit. They'll be reconstructed. while let Some(transform) = cursor.item() { - if transform.block.as_ref().map_or(false, |b| b.place_below()) { + if transform.block.as_ref().is_some_and(|b| b.place_below()) { cursor.next(); } else { break; @@ -657,10 +653,10 @@ impl BlockMap { .iter() .filter_map(|block| { let placement = block.placement.to_wrap_row(wrap_snapshot)?; - if let BlockPlacement::Above(row) = placement { - if row < new_start { - return None; - } + if let BlockPlacement::Above(row) = placement + && row < new_start + { + return None; } Some((placement, Block::Custom(block.clone()))) }), @@ -977,10 +973,10 @@ impl BlockMapReader<'_> { break; } - if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id()) { - if id == block_id { - return Some(cursor.start().1); - } + if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id()) + && id == block_id + { + return Some(cursor.start().1); } cursor.next(); } @@ -1299,14 +1295,14 @@ impl BlockSnapshot { let mut input_start = transform_input_start; let mut input_end = transform_input_start; - if let Some(transform) = cursor.item() { - if transform.block.is_none() { - input_start += rows.start - transform_output_start; - input_end += cmp::min( - rows.end - transform_output_start, - transform.summary.input_rows, - ); - } + if let Some(transform) = cursor.item() + && transform.block.is_none() + { + input_start += rows.start - transform_output_start; + input_end += cmp::min( + rows.end - transform_output_start, + transform.summary.input_rows, + ); } BlockChunks { @@ -1329,7 +1325,7 @@ impl BlockSnapshot { let Dimensions(output_start, input_start, _) = cursor.start(); let overshoot = if cursor .item() - .map_or(false, |transform| transform.block.is_none()) + .is_some_and(|transform| transform.block.is_none()) { start_row.0 - output_start.0 } else { @@ -1359,7 +1355,7 @@ impl BlockSnapshot { && transform .block .as_ref() - .map_or(false, |block| block.height() > 0)) + .is_some_and(|block| block.height() > 0)) { break; } @@ -1472,18 +1468,18 @@ impl BlockSnapshot { longest_row_chars = summary.longest_row_chars; } - if let Some(transform) = cursor.item() { - if transform.block.is_none() { - let Dimensions(output_start, input_start, _) = cursor.start(); - let overshoot = range.end.0 - output_start.0; - let wrap_start_row = input_start.0; - let wrap_end_row = input_start.0 + overshoot; - let summary = self - .wrap_snapshot - .text_summary_for_range(wrap_start_row..wrap_end_row); - if summary.longest_row_chars > longest_row_chars { - longest_row = BlockRow(output_start.0 + summary.longest_row); - } + if let Some(transform) = cursor.item() + && transform.block.is_none() + { + let Dimensions(output_start, input_start, _) = cursor.start(); + let overshoot = range.end.0 - output_start.0; + let wrap_start_row = input_start.0; + let wrap_end_row = input_start.0 + overshoot; + let summary = self + .wrap_snapshot + .text_summary_for_range(wrap_start_row..wrap_end_row); + if summary.longest_row_chars > longest_row_chars { + longest_row = BlockRow(output_start.0 + summary.longest_row); } } } @@ -1512,7 +1508,7 @@ impl BlockSnapshot { pub(super) fn is_block_line(&self, row: BlockRow) -> bool { let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&()); cursor.seek(&row, Bias::Right); - cursor.item().map_or(false, |t| t.block.is_some()) + cursor.item().is_some_and(|t| t.block.is_some()) } pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool { @@ -1530,11 +1526,11 @@ impl BlockSnapshot { .make_wrap_point(Point::new(row.0, 0), Bias::Left); let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&()); cursor.seek(&WrapRow(wrap_point.row()), Bias::Right); - cursor.item().map_or(false, |transform| { + cursor.item().is_some_and(|transform| { transform .block .as_ref() - .map_or(false, |block| block.is_replacement()) + .is_some_and(|block| block.is_replacement()) }) } @@ -1557,12 +1553,11 @@ impl BlockSnapshot { match transform.block.as_ref() { Some(block) => { - if block.is_replacement() { - if ((bias == Bias::Left || search_left) && output_start <= point.0) - || (!search_left && output_start >= point.0) - { - return BlockPoint(output_start); - } + if block.is_replacement() + && (((bias == Bias::Left || search_left) && output_start <= point.0) + || (!search_left && output_start >= point.0)) + { + return BlockPoint(output_start); } } None => { @@ -1655,7 +1650,7 @@ impl BlockChunks<'_> { if transform .block .as_ref() - .map_or(false, |block| block.height() == 0) + .is_some_and(|block| block.height() == 0) { self.transforms.next(); } else { @@ -1666,7 +1661,7 @@ impl BlockChunks<'_> { if self .transforms .item() - .map_or(false, |transform| transform.block.is_none()) + .is_some_and(|transform| transform.block.is_none()) { let start_input_row = self.transforms.start().1.0; let start_output_row = self.transforms.start().0.0; @@ -1776,7 +1771,7 @@ impl Iterator for BlockRows<'_> { if transform .block .as_ref() - .map_or(false, |block| block.height() == 0) + .is_some_and(|block| block.height() == 0) { self.transforms.next(); } else { @@ -1788,7 +1783,7 @@ impl Iterator for BlockRows<'_> { if transform .block .as_ref() - .map_or(true, |block| block.is_replacement()) + .is_none_or(|block| block.is_replacement()) { self.input_rows.seek(self.transforms.start().1.0); } @@ -3228,34 +3223,32 @@ mod tests { let mut is_in_replace_block = false; if let Some((BlockPlacement::Replace(replace_range), block)) = sorted_blocks_iter.peek() + && wrap_row >= replace_range.start().0 { - if wrap_row >= replace_range.start().0 { - is_in_replace_block = true; + is_in_replace_block = true; - if wrap_row == replace_range.start().0 { - if matches!(block, Block::FoldedBuffer { .. }) { - expected_buffer_rows.push(None); - } else { - expected_buffer_rows - .push(input_buffer_rows[multibuffer_row as usize]); - } + if wrap_row == replace_range.start().0 { + if matches!(block, Block::FoldedBuffer { .. }) { + expected_buffer_rows.push(None); + } else { + expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]); } + } - if wrap_row == replace_range.end().0 { - expected_block_positions.push((block_row, block.id())); - let text = "\n".repeat((block.height() - 1) as usize); - if block_row > 0 { - expected_text.push('\n'); - } - expected_text.push_str(&text); - - for _ in 1..block.height() { - expected_buffer_rows.push(None); - } - block_row += block.height(); - - sorted_blocks_iter.next(); + if wrap_row == replace_range.end().0 { + expected_block_positions.push((block_row, block.id())); + let text = "\n".repeat((block.height() - 1) as usize); + if block_row > 0 { + expected_text.push('\n'); } + expected_text.push_str(&text); + + for _ in 1..block.height() { + expected_buffer_rows.push(None); + } + block_row += block.height(); + + sorted_blocks_iter.next(); } } diff --git a/crates/editor/src/display_map/custom_highlights.rs b/crates/editor/src/display_map/custom_highlights.rs index ae69e9cf8c..f3737ea4b7 100644 --- a/crates/editor/src/display_map/custom_highlights.rs +++ b/crates/editor/src/display_map/custom_highlights.rs @@ -77,7 +77,7 @@ fn create_highlight_endpoints( let ranges = &text_highlights.1; let start_ix = match ranges.binary_search_by(|probe| { - let cmp = probe.end.cmp(&start, &buffer); + let cmp = probe.end.cmp(&start, buffer); if cmp.is_gt() { cmp::Ordering::Greater } else { @@ -88,18 +88,18 @@ fn create_highlight_endpoints( }; for range in &ranges[start_ix..] { - if range.start.cmp(&end, &buffer).is_ge() { + if range.start.cmp(&end, buffer).is_ge() { break; } highlight_endpoints.push(HighlightEndpoint { - offset: range.start.to_offset(&buffer), + offset: range.start.to_offset(buffer), is_start: true, tag, style, }); highlight_endpoints.push(HighlightEndpoint { - offset: range.end.to_offset(&buffer), + offset: range.end.to_offset(buffer), is_start: false, tag, style, diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index c4e53a0f43..3dcd172c3c 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -289,25 +289,25 @@ impl FoldMapWriter<'_> { let ChunkRendererId::Fold(id) = id else { continue; }; - if let Some(metadata) = self.0.snapshot.fold_metadata_by_id.get(&id).cloned() { - if Some(new_width) != metadata.width { - let buffer_start = metadata.range.start.to_offset(buffer); - let buffer_end = metadata.range.end.to_offset(buffer); - let inlay_range = inlay_snapshot.to_inlay_offset(buffer_start) - ..inlay_snapshot.to_inlay_offset(buffer_end); - edits.push(InlayEdit { - old: inlay_range.clone(), - new: inlay_range.clone(), - }); + if let Some(metadata) = self.0.snapshot.fold_metadata_by_id.get(&id).cloned() + && Some(new_width) != metadata.width + { + let buffer_start = metadata.range.start.to_offset(buffer); + let buffer_end = metadata.range.end.to_offset(buffer); + let inlay_range = inlay_snapshot.to_inlay_offset(buffer_start) + ..inlay_snapshot.to_inlay_offset(buffer_end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range.clone(), + }); - self.0.snapshot.fold_metadata_by_id.insert( - id, - FoldMetadata { - range: metadata.range, - width: Some(new_width), - }, - ); - } + self.0.snapshot.fold_metadata_by_id.insert( + id, + FoldMetadata { + range: metadata.range, + width: Some(new_width), + }, + ); } } @@ -417,18 +417,18 @@ impl FoldMap { cursor.seek(&InlayOffset(0), Bias::Right); while let Some(mut edit) = inlay_edits_iter.next() { - if let Some(item) = cursor.item() { - if !item.is_fold() { - new_transforms.update_last( - |transform| { - if !transform.is_fold() { - transform.summary.add_summary(&item.summary, &()); - cursor.next(); - } - }, - &(), - ); - } + if let Some(item) = cursor.item() + && !item.is_fold() + { + new_transforms.update_last( + |transform| { + if !transform.is_fold() { + transform.summary.add_summary(&item.summary, &()); + cursor.next(); + } + }, + &(), + ); } new_transforms.append(cursor.slice(&edit.old.start, Bias::Left), &()); edit.new.start -= edit.old.start - *cursor.start(); @@ -491,14 +491,14 @@ impl FoldMap { while folds .peek() - .map_or(false, |(_, fold_range)| fold_range.start < edit.new.end) + .is_some_and(|(_, fold_range)| fold_range.start < edit.new.end) { let (fold, mut fold_range) = folds.next().unwrap(); let sum = new_transforms.summary(); assert!(fold_range.start.0 >= sum.input.len); - while folds.peek().map_or(false, |(next_fold, next_fold_range)| { + while folds.peek().is_some_and(|(next_fold, next_fold_range)| { next_fold_range.start < fold_range.end || (next_fold_range.start == fold_range.end && fold.placeholder.merge_adjacent @@ -575,14 +575,14 @@ impl FoldMap { for mut edit in inlay_edits { old_transforms.seek(&edit.old.start, Bias::Left); - if old_transforms.item().map_or(false, |t| t.is_fold()) { + if old_transforms.item().is_some_and(|t| t.is_fold()) { edit.old.start = old_transforms.start().0; } let old_start = old_transforms.start().1.0 + (edit.old.start - old_transforms.start().0).0; old_transforms.seek_forward(&edit.old.end, Bias::Right); - if old_transforms.item().map_or(false, |t| t.is_fold()) { + if old_transforms.item().is_some_and(|t| t.is_fold()) { old_transforms.next(); edit.old.end = old_transforms.start().0; } @@ -590,14 +590,14 @@ impl FoldMap { old_transforms.start().1.0 + (edit.old.end - old_transforms.start().0).0; new_transforms.seek(&edit.new.start, Bias::Left); - if new_transforms.item().map_or(false, |t| t.is_fold()) { + if new_transforms.item().is_some_and(|t| t.is_fold()) { edit.new.start = new_transforms.start().0; } let new_start = new_transforms.start().1.0 + (edit.new.start - new_transforms.start().0).0; new_transforms.seek_forward(&edit.new.end, Bias::Right); - if new_transforms.item().map_or(false, |t| t.is_fold()) { + if new_transforms.item().is_some_and(|t| t.is_fold()) { new_transforms.next(); edit.new.end = new_transforms.start().0; } @@ -709,7 +709,7 @@ impl FoldSnapshot { .transforms .cursor::<Dimensions<InlayPoint, FoldPoint>>(&()); cursor.seek(&point, Bias::Right); - if cursor.item().map_or(false, |t| t.is_fold()) { + if cursor.item().is_some_and(|t| t.is_fold()) { if bias == Bias::Left || point == cursor.start().0 { cursor.start().1 } else { @@ -788,7 +788,7 @@ impl FoldSnapshot { let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset); let mut cursor = self.transforms.cursor::<InlayOffset>(&()); cursor.seek(&inlay_offset, Bias::Right); - cursor.item().map_or(false, |t| t.placeholder.is_some()) + cursor.item().is_some_and(|t| t.placeholder.is_some()) } pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool { @@ -839,7 +839,7 @@ impl FoldSnapshot { let inlay_end = if transform_cursor .item() - .map_or(true, |transform| transform.is_fold()) + .is_none_or(|transform| transform.is_fold()) { inlay_start } else if range.end < transform_end.0 { @@ -1348,7 +1348,7 @@ impl FoldChunks<'_> { let inlay_end = if self .transform_cursor .item() - .map_or(true, |transform| transform.is_fold()) + .is_none_or(|transform| transform.is_fold()) { inlay_start } else if range.end < transform_end.0 { @@ -1463,7 +1463,7 @@ impl FoldOffset { .transforms .cursor::<Dimensions<FoldOffset, TransformSummary>>(&()); cursor.seek(&self, Bias::Right); - let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) { + let overshoot = if cursor.item().is_none_or(|t| t.is_fold()) { Point::new(0, (self.0 - cursor.start().0.0) as u32) } else { let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0.0; diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 76148af587..3db9d10fdc 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -557,11 +557,11 @@ impl InlayMap { let mut buffer_edits_iter = buffer_edits.iter().peekable(); while let Some(buffer_edit) = buffer_edits_iter.next() { new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left), &()); - if let Some(Transform::Isomorphic(transform)) = cursor.item() { - if cursor.end().0 == buffer_edit.old.start { - push_isomorphic(&mut new_transforms, *transform); - cursor.next(); - } + if let Some(Transform::Isomorphic(transform)) = cursor.item() + && cursor.end().0 == buffer_edit.old.start + { + push_isomorphic(&mut new_transforms, *transform); + cursor.next(); } // Remove all the inlays and transforms contained by the edit. @@ -625,7 +625,7 @@ impl InlayMap { // we can push its remainder. if buffer_edits_iter .peek() - .map_or(true, |edit| edit.old.start >= cursor.end().0) + .is_none_or(|edit| edit.old.start >= cursor.end().0) { let transform_start = new_transforms.summary().input.len; let transform_end = diff --git a/crates/editor/src/display_map/invisibles.rs b/crates/editor/src/display_map/invisibles.rs index 199986f2a4..0712ddf9e2 100644 --- a/crates/editor/src/display_map/invisibles.rs +++ b/crates/editor/src/display_map/invisibles.rs @@ -36,8 +36,8 @@ pub fn is_invisible(c: char) -> bool { } else if c >= '\u{7f}' { c <= '\u{9f}' || (c.is_whitespace() && c != IDEOGRAPHIC_SPACE) - || contains(c, &FORMAT) - || contains(c, &OTHER) + || contains(c, FORMAT) + || contains(c, OTHER) } else { false } @@ -50,7 +50,7 @@ pub fn replacement(c: char) -> Option<&'static str> { Some(C0_SYMBOLS[c as usize]) } else if c == '\x7f' { Some(DEL) - } else if contains(c, &PRESERVE) { + } else if contains(c, PRESERVE) { None } else { Some("\u{2007}") // fixed width space @@ -61,14 +61,14 @@ pub fn replacement(c: char) -> Option<&'static str> { // but could if we tracked state in the classifier. const IDEOGRAPHIC_SPACE: char = '\u{3000}'; -const C0_SYMBOLS: &'static [&'static str] = &[ +const C0_SYMBOLS: &[&str] = &[ "␀", "␁", "␂", "␃", "␄", "␅", "␆", "␇", "␈", "␉", "␊", "␋", "␌", "␍", "␎", "␏", "␐", "␑", "␒", "␓", "␔", "␕", "␖", "␗", "␘", "␙", "␚", "␛", "␜", "␝", "␞", "␟", ]; -const DEL: &'static str = "␡"; +const DEL: &str = "␡"; // generated using ucd-generate: ucd-generate general-category --include Format --chars ucd-16.0.0 -pub const FORMAT: &'static [(char, char)] = &[ +pub const FORMAT: &[(char, char)] = &[ ('\u{ad}', '\u{ad}'), ('\u{600}', '\u{605}'), ('\u{61c}', '\u{61c}'), @@ -93,7 +93,7 @@ pub const FORMAT: &'static [(char, char)] = &[ ]; // hand-made base on https://invisible-characters.com (Excluding Cf) -pub const OTHER: &'static [(char, char)] = &[ +pub const OTHER: &[(char, char)] = &[ ('\u{034f}', '\u{034f}'), ('\u{115F}', '\u{1160}'), ('\u{17b4}', '\u{17b5}'), @@ -107,7 +107,7 @@ pub const OTHER: &'static [(char, char)] = &[ ]; // a subset of FORMAT/OTHER that may appear within glyphs -const PRESERVE: &'static [(char, char)] = &[ +const PRESERVE: &[(char, char)] = &[ ('\u{034f}', '\u{034f}'), ('\u{200d}', '\u{200d}'), ('\u{17b4}', '\u{17b5}'), diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index caa4882a6e..500ec3a0bb 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -74,10 +74,10 @@ impl WrapRows<'_> { self.transforms .seek(&WrapPoint::new(start_row, 0), Bias::Left); let mut input_row = self.transforms.start().1.row(); - if self.transforms.item().map_or(false, |t| t.is_isomorphic()) { + if self.transforms.item().is_some_and(|t| t.is_isomorphic()) { input_row += start_row - self.transforms.start().0.row(); } - self.soft_wrapped = self.transforms.item().map_or(false, |t| !t.is_isomorphic()); + self.soft_wrapped = self.transforms.item().is_some_and(|t| !t.is_isomorphic()); self.input_buffer_rows.seek(input_row); self.input_buffer_row = self.input_buffer_rows.next().unwrap(); self.output_row = start_row; @@ -249,48 +249,48 @@ impl WrapMap { return; } - if let Some(wrap_width) = self.wrap_width { - if self.background_task.is_none() { - let pending_edits = self.pending_edits.clone(); - let mut snapshot = self.snapshot.clone(); - let text_system = cx.text_system().clone(); - let (font, font_size) = self.font_with_size.clone(); - let update_task = cx.background_spawn(async move { - let mut edits = Patch::default(); - let mut line_wrapper = text_system.line_wrapper(font, font_size); - for (tab_snapshot, tab_edits) in pending_edits { - let wrap_edits = snapshot - .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) - .await; - edits = edits.compose(&wrap_edits); - } - (snapshot, edits) - }); + if let Some(wrap_width) = self.wrap_width + && self.background_task.is_none() + { + let pending_edits = self.pending_edits.clone(); + let mut snapshot = self.snapshot.clone(); + let text_system = cx.text_system().clone(); + let (font, font_size) = self.font_with_size.clone(); + let update_task = cx.background_spawn(async move { + let mut edits = Patch::default(); + let mut line_wrapper = text_system.line_wrapper(font, font_size); + for (tab_snapshot, tab_edits) in pending_edits { + let wrap_edits = snapshot + .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) + .await; + edits = edits.compose(&wrap_edits); + } + (snapshot, edits) + }); - match cx - .background_executor() - .block_with_timeout(Duration::from_millis(1), update_task) - { - Ok((snapshot, output_edits)) => { - self.snapshot = snapshot; - self.edits_since_sync = self.edits_since_sync.compose(&output_edits); - } - Err(update_task) => { - self.background_task = Some(cx.spawn(async move |this, cx| { - let (snapshot, edits) = update_task.await; - this.update(cx, |this, cx| { - this.snapshot = snapshot; - this.edits_since_sync = this - .edits_since_sync - .compose(mem::take(&mut this.interpolated_edits).invert()) - .compose(&edits); - this.background_task = None; - this.flush_edits(cx); - cx.notify(); - }) - .ok(); - })); - } + match cx + .background_executor() + .block_with_timeout(Duration::from_millis(1), update_task) + { + Ok((snapshot, output_edits)) => { + self.snapshot = snapshot; + self.edits_since_sync = self.edits_since_sync.compose(&output_edits); + } + Err(update_task) => { + self.background_task = Some(cx.spawn(async move |this, cx| { + let (snapshot, edits) = update_task.await; + this.update(cx, |this, cx| { + this.snapshot = snapshot; + this.edits_since_sync = this + .edits_since_sync + .compose(mem::take(&mut this.interpolated_edits).invert()) + .compose(&edits); + this.background_task = None; + this.flush_edits(cx); + cx.notify(); + }) + .ok(); + })); } } } @@ -603,7 +603,7 @@ impl WrapSnapshot { .cursor::<Dimensions<WrapPoint, TabPoint>>(&()); transforms.seek(&output_start, Bias::Right); let mut input_start = TabPoint(transforms.start().1.0); - if transforms.item().map_or(false, |t| t.is_isomorphic()) { + if transforms.item().is_some_and(|t| t.is_isomorphic()) { input_start.0 += output_start.0 - transforms.start().0.0; } let input_end = self @@ -634,7 +634,7 @@ impl WrapSnapshot { cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left); if cursor .item() - .map_or(false, |transform| transform.is_isomorphic()) + .is_some_and(|transform| transform.is_isomorphic()) { let overshoot = row - cursor.start().0.row(); let tab_row = cursor.start().1.row() + overshoot; @@ -732,10 +732,10 @@ impl WrapSnapshot { .cursor::<Dimensions<WrapPoint, TabPoint>>(&()); transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left); let mut input_row = transforms.start().1.row(); - if transforms.item().map_or(false, |t| t.is_isomorphic()) { + if transforms.item().is_some_and(|t| t.is_isomorphic()) { input_row += start_row - transforms.start().0.row(); } - let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic()); + let soft_wrapped = transforms.item().is_some_and(|t| !t.is_isomorphic()); let mut input_buffer_rows = self.tab_snapshot.rows(input_row); let input_buffer_row = input_buffer_rows.next().unwrap(); WrapRows { @@ -754,7 +754,7 @@ impl WrapSnapshot { .cursor::<Dimensions<WrapPoint, TabPoint>>(&()); cursor.seek(&point, Bias::Right); let mut tab_point = cursor.start().1.0; - if cursor.item().map_or(false, |t| t.is_isomorphic()) { + if cursor.item().is_some_and(|t| t.is_isomorphic()) { tab_point += point.0 - cursor.start().0.0; } TabPoint(tab_point) @@ -780,7 +780,7 @@ impl WrapSnapshot { if bias == Bias::Left { let mut cursor = self.transforms.cursor::<WrapPoint>(&()); cursor.seek(&point, Bias::Right); - if cursor.item().map_or(false, |t| !t.is_isomorphic()) { + if cursor.item().is_some_and(|t| !t.is_isomorphic()) { point = *cursor.start(); *point.column_mut() -= 1; } @@ -901,7 +901,7 @@ impl WrapChunks<'_> { let output_end = WrapPoint::new(rows.end, 0); self.transforms.seek(&output_start, Bias::Right); let mut input_start = TabPoint(self.transforms.start().1.0); - if self.transforms.item().map_or(false, |t| t.is_isomorphic()) { + if self.transforms.item().is_some_and(|t| t.is_isomorphic()) { input_start.0 += output_start.0 - self.transforms.start().0.0; } let input_end = self @@ -993,7 +993,7 @@ impl Iterator for WrapRows<'_> { self.output_row += 1; self.transforms .seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left); - if self.transforms.item().map_or(false, |t| t.is_isomorphic()) { + if self.transforms.item().is_some_and(|t| t.is_isomorphic()) { self.input_buffer_row = self.input_buffer_rows.next().unwrap(); self.soft_wrapped = false; } else { @@ -1065,12 +1065,12 @@ impl sum_tree::Item for Transform { } fn push_isomorphic(transforms: &mut Vec<Transform>, summary: TextSummary) { - if let Some(last_transform) = transforms.last_mut() { - if last_transform.is_isomorphic() { - last_transform.summary.input += &summary; - last_transform.summary.output += &summary; - return; - } + if let Some(last_transform) = transforms.last_mut() + && last_transform.is_isomorphic() + { + last_transform.summary.input += &summary; + last_transform.summary.output += &summary; + return; } transforms.push(Transform::isomorphic(summary)); } @@ -1461,7 +1461,7 @@ mod tests { } let mut prev_ix = 0; - for boundary in line_wrapper.wrap_line(&[LineFragment::text(&line)], wrap_width) { + for boundary in line_wrapper.wrap_line(&[LineFragment::text(line)], wrap_width) { wrapped_text.push_str(&line[prev_ix..boundary.ix]); wrapped_text.push('\n'); wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize)); diff --git a/crates/editor/src/edit_prediction_tests.rs b/crates/editor/src/edit_prediction_tests.rs index 7bf51e45d7..bba632e81f 100644 --- a/crates/editor/src/edit_prediction_tests.rs +++ b/crates/editor/src/edit_prediction_tests.rs @@ -7,7 +7,9 @@ use std::ops::Range; use text::{Point, ToOffset}; use crate::{ - EditPrediction, editor_tests::init_test, test::editor_test_context::EditorTestContext, + EditPrediction, + editor_tests::{init_test, update_test_language_settings}, + test::editor_test_context::EditorTestContext, }; #[gpui::test] @@ -271,6 +273,44 @@ async fn test_edit_prediction_jump_disabled_for_non_zed_providers(cx: &mut gpui: }); } +#[gpui::test] +async fn test_edit_predictions_disabled_in_scope(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + update_test_language_settings(cx, |settings| { + settings.defaults.edit_predictions_disabled_in = Some(vec!["string".to_string()]); + }); + + let mut cx = EditorTestContext::new(cx).await; + let provider = cx.new(|_| FakeEditPredictionProvider::default()); + assign_editor_completion_provider(provider.clone(), &mut cx); + + let language = languages::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into()); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + + // Test disabled inside of string + cx.set_state("const x = \"hello ˇworld\";"); + propose_edits(&provider, vec![(17..17, "beautiful ")], &mut cx); + cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.editor(|editor, _, _| { + assert!( + editor.active_edit_prediction.is_none(), + "Edit predictions should be disabled in string scopes when configured in edit_predictions_disabled_in" + ); + }); + + // Test enabled outside of string + cx.set_state("const x = \"hello world\"; ˇ"); + propose_edits(&provider, vec![(24..24, "// comment")], &mut cx); + cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx)); + cx.editor(|editor, _, _| { + assert!( + editor.active_edit_prediction.is_some(), + "Edit predictions should work outside of disabled scopes" + ); + }); +} + fn assert_editor_active_edit_completion( cx: &mut EditorTestContext, assert: impl FnOnce(MultiBufferSnapshot, &Vec<(Range<Anchor>, String)>), diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9c8f0ad3a4..b3ebfd7745 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -942,10 +942,10 @@ impl ChangeList { } pub fn invert_last_group(&mut self) { - if let Some(last) = self.changes.last_mut() { - if let Some(current) = last.current.as_mut() { - mem::swap(&mut last.original, current); - } + if let Some(last) = self.changes.last_mut() + && let Some(current) = last.current.as_mut() + { + mem::swap(&mut last.original, current); } } } @@ -1429,7 +1429,7 @@ impl SelectionHistory { if self .undo_stack .back() - .map_or(true, |e| e.selections != entry.selections) + .is_none_or(|e| e.selections != entry.selections) { self.undo_stack.push_back(entry); if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN { @@ -1442,7 +1442,7 @@ impl SelectionHistory { if self .redo_stack .back() - .map_or(true, |e| e.selections != entry.selections) + .is_none_or(|e| e.selections != entry.selections) { self.redo_stack.push_back(entry); if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN { @@ -1861,114 +1861,108 @@ impl Editor { .then(|| language_settings::SoftWrap::None); let mut project_subscriptions = Vec::new(); - if full_mode { - if let Some(project) = project.as_ref() { - project_subscriptions.push(cx.subscribe_in( - project, - window, - |editor, _, event, window, cx| match event { - project::Event::RefreshCodeLens => { - // we always query lens with actions, without storing them, always refreshing them + if full_mode && let Some(project) = project.as_ref() { + project_subscriptions.push(cx.subscribe_in( + project, + window, + |editor, _, event, window, cx| match event { + project::Event::RefreshCodeLens => { + // we always query lens with actions, without storing them, always refreshing them + } + project::Event::RefreshInlayHints => { + editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx); + } + project::Event::LanguageServerAdded(..) + | project::Event::LanguageServerRemoved(..) => { + if editor.tasks_update_task.is_none() { + editor.tasks_update_task = Some(editor.refresh_runnables(window, cx)); } - project::Event::RefreshInlayHints => { - editor - .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx); - } - project::Event::LanguageServerAdded(..) - | project::Event::LanguageServerRemoved(..) => { - if editor.tasks_update_task.is_none() { - editor.tasks_update_task = - Some(editor.refresh_runnables(window, cx)); - } - } - project::Event::SnippetEdit(id, snippet_edits) => { - if let Some(buffer) = editor.buffer.read(cx).buffer(*id) { - let focus_handle = editor.focus_handle(cx); - if focus_handle.is_focused(window) { - let snapshot = buffer.read(cx).snapshot(); - for (range, snippet) in snippet_edits { - let editor_range = - language::range_from_lsp(*range).to_offset(&snapshot); - editor - .insert_snippet( - &[editor_range], - snippet.clone(), - window, - cx, - ) - .ok(); - } + } + project::Event::SnippetEdit(id, snippet_edits) => { + if let Some(buffer) = editor.buffer.read(cx).buffer(*id) { + let focus_handle = editor.focus_handle(cx); + if focus_handle.is_focused(window) { + let snapshot = buffer.read(cx).snapshot(); + for (range, snippet) in snippet_edits { + let editor_range = + language::range_from_lsp(*range).to_offset(&snapshot); + editor + .insert_snippet( + &[editor_range], + snippet.clone(), + window, + cx, + ) + .ok(); } } } - project::Event::LanguageServerBufferRegistered { buffer_id, .. } => { - if editor.buffer().read(cx).buffer(*buffer_id).is_some() { - editor.update_lsp_data(false, Some(*buffer_id), window, cx); - } - } - _ => {} - }, - )); - if let Some(task_inventory) = project - .read(cx) - .task_store() - .read(cx) - .task_inventory() - .cloned() - { - project_subscriptions.push(cx.observe_in( - &task_inventory, - window, - |editor, _, window, cx| { - editor.tasks_update_task = Some(editor.refresh_runnables(window, cx)); - }, - )); - }; - - project_subscriptions.push(cx.subscribe_in( - &project.read(cx).breakpoint_store(), - window, - |editor, _, event, window, cx| match event { - BreakpointStoreEvent::ClearDebugLines => { - editor.clear_row_highlights::<ActiveDebugLine>(); - editor.refresh_inline_values(cx); - } - BreakpointStoreEvent::SetDebugLine => { - if editor.go_to_active_debug_line(window, cx) { - cx.stop_propagation(); - } - - editor.refresh_inline_values(cx); - } - _ => {} - }, - )); - let git_store = project.read(cx).git_store().clone(); - let project = project.clone(); - project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| { - match event { - GitStoreEvent::RepositoryUpdated( - _, - RepositoryEvent::Updated { - new_instance: true, .. - }, - _, - ) => { - this.load_diff_task = Some( - update_uncommitted_diff_for_buffer( - cx.entity(), - &project, - this.buffer.read(cx).all_buffers(), - this.buffer.clone(), - cx, - ) - .shared(), - ); - } - _ => {} } - })); - } + project::Event::LanguageServerBufferRegistered { buffer_id, .. } => { + if editor.buffer().read(cx).buffer(*buffer_id).is_some() { + editor.update_lsp_data(false, Some(*buffer_id), window, cx); + } + } + _ => {} + }, + )); + if let Some(task_inventory) = project + .read(cx) + .task_store() + .read(cx) + .task_inventory() + .cloned() + { + project_subscriptions.push(cx.observe_in( + &task_inventory, + window, + |editor, _, window, cx| { + editor.tasks_update_task = Some(editor.refresh_runnables(window, cx)); + }, + )); + }; + + project_subscriptions.push(cx.subscribe_in( + &project.read(cx).breakpoint_store(), + window, + |editor, _, event, window, cx| match event { + BreakpointStoreEvent::ClearDebugLines => { + editor.clear_row_highlights::<ActiveDebugLine>(); + editor.refresh_inline_values(cx); + } + BreakpointStoreEvent::SetDebugLine => { + if editor.go_to_active_debug_line(window, cx) { + cx.stop_propagation(); + } + + editor.refresh_inline_values(cx); + } + _ => {} + }, + )); + let git_store = project.read(cx).git_store().clone(); + let project = project.clone(); + project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| { + if let GitStoreEvent::RepositoryUpdated( + _, + RepositoryEvent::Updated { + new_instance: true, .. + }, + _, + ) = event + { + this.load_diff_task = Some( + update_uncommitted_diff_for_buffer( + cx.entity(), + &project, + this.buffer.read(cx).all_buffers(), + this.buffer.clone(), + cx, + ) + .shared(), + ); + } + })); } let buffer_snapshot = buffer.read(cx).snapshot(cx); @@ -2323,15 +2317,15 @@ impl Editor { editor.go_to_active_debug_line(window, cx); - if let Some(buffer) = buffer.read(cx).as_singleton() { - if let Some(project) = editor.project() { - let handle = project.update(cx, |project, cx| { - project.register_buffer_with_language_servers(&buffer, cx) - }); - editor - .registered_buffers - .insert(buffer.read(cx).remote_id(), handle); - } + if let Some(buffer) = buffer.read(cx).as_singleton() + && let Some(project) = editor.project() + { + let handle = project.update(cx, |project, cx| { + project.register_buffer_with_language_servers(&buffer, cx) + }); + editor + .registered_buffers + .insert(buffer.read(cx).remote_id(), handle); } editor.minimap = @@ -2379,7 +2373,7 @@ impl Editor { pending_selection .selection .range() - .includes(&range, &snapshot) + .includes(range, &snapshot) }) { return true; @@ -2516,9 +2510,7 @@ impl Editor { .context_menu .borrow() .as_ref() - .map_or(false, |context| { - matches!(context, CodeContextMenu::Completions(_)) - }); + .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_))); showing_completions || self.edit_prediction_requires_modifier() @@ -2549,7 +2541,7 @@ impl Editor { || binding .keystrokes() .first() - .map_or(false, |keystroke| keystroke.modifiers.modified()) + .is_some_and(|keystroke| keystroke.modifiers.modified()) })) } @@ -2945,7 +2937,7 @@ impl Editor { return false; }; - scope.override_name().map_or(false, |scope_name| { + scope.override_name().is_some_and(|scope_name| { settings .edit_predictions_disabled_in .iter() @@ -3035,20 +3027,19 @@ impl Editor { } if local { - if let Some(buffer_id) = new_cursor_position.buffer_id { - if !self.registered_buffers.contains_key(&buffer_id) { - if let Some(project) = self.project.as_ref() { - project.update(cx, |project, cx| { - let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else { - return; - }; - self.registered_buffers.insert( - buffer_id, - project.register_buffer_with_language_servers(&buffer, cx), - ); - }) - } - } + if let Some(buffer_id) = new_cursor_position.buffer_id + && !self.registered_buffers.contains_key(&buffer_id) + && let Some(project) = self.project.as_ref() + { + project.update(cx, |project, cx| { + let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else { + return; + }; + self.registered_buffers.insert( + buffer_id, + project.register_buffer_with_language_servers(&buffer, cx), + ); + }) } let mut context_menu = self.context_menu.borrow_mut(); @@ -3063,28 +3054,28 @@ impl Editor { let completion_position = completion_menu.map(|menu| menu.initial_position); drop(context_menu); - if effects.completions { - if let Some(completion_position) = completion_position { - let start_offset = selection_start.to_offset(buffer); - let position_matches = start_offset == completion_position.to_offset(buffer); - let continue_showing = if position_matches { - if self.snippet_stack.is_empty() { - buffer.char_kind_before(start_offset, true) == Some(CharKind::Word) - } else { - // Snippet choices can be shown even when the cursor is in whitespace. - // Dismissing the menu with actions like backspace is handled by - // invalidation regions. - true - } + if effects.completions + && let Some(completion_position) = completion_position + { + let start_offset = selection_start.to_offset(buffer); + let position_matches = start_offset == completion_position.to_offset(buffer); + let continue_showing = if position_matches { + if self.snippet_stack.is_empty() { + buffer.char_kind_before(start_offset, true) == Some(CharKind::Word) } else { - false - }; - - if continue_showing { - self.show_completions(&ShowCompletions { trigger: None }, window, cx); - } else { - self.hide_context_menu(window, cx); + // Snippet choices can be shown even when the cursor is in whitespace. + // Dismissing the menu with actions like backspace is handled by + // invalidation regions. + true } + } else { + false + }; + + if continue_showing { + self.show_completions(&ShowCompletions { trigger: None }, window, cx); + } else { + self.hide_context_menu(window, cx); } } @@ -3115,30 +3106,27 @@ impl Editor { if selections.len() == 1 { cx.emit(SearchEvent::ActiveMatchChanged) } - if local { - if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() { - let inmemory_selections = selections - .iter() - .map(|s| { - text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot) - ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot) - }) - .collect(); - self.update_restoration_data(cx, |data| { - data.selections = inmemory_selections; - }); + if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() { + let inmemory_selections = selections + .iter() + .map(|s| { + text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot) + ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot) + }) + .collect(); + self.update_restoration_data(cx, |data| { + data.selections = inmemory_selections; + }); - if WorkspaceSettings::get(None, cx).restore_on_startup - != RestoreOnStartupBehavior::None - { - if let Some(workspace_id) = - self.workspace.as_ref().and_then(|workspace| workspace.1) - { - let snapshot = self.buffer().read(cx).snapshot(cx); - let selections = selections.clone(); - let background_executor = cx.background_executor().clone(); - let editor_id = cx.entity().entity_id().as_u64() as ItemId; - self.serialize_selections = cx.background_spawn(async move { + if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None + && let Some(workspace_id) = + self.workspace.as_ref().and_then(|workspace| workspace.1) + { + let snapshot = self.buffer().read(cx).snapshot(cx); + let selections = selections.clone(); + let background_executor = cx.background_executor().clone(); + let editor_id = cx.entity().entity_id().as_u64() as ItemId; + self.serialize_selections = cx.background_spawn(async move { background_executor.timer(SERIALIZATION_THROTTLE_TIME).await; let db_selections = selections .iter() @@ -3155,8 +3143,6 @@ impl Editor { .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}")) .log_err(); }); - } - } } } @@ -3233,35 +3219,31 @@ impl Editor { selections.select_anchors(other_selections); }); - let other_subscription = - cx.subscribe(&other, |this, other, other_evt, cx| match other_evt { - EditorEvent::SelectionsChanged { local: true } => { - let other_selections = other.read(cx).selections.disjoint.to_vec(); - if other_selections.is_empty() { - return; - } - this.selections.change_with(cx, |selections| { - selections.select_anchors(other_selections); - }); + let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| { + if let EditorEvent::SelectionsChanged { local: true } = other_evt { + let other_selections = other.read(cx).selections.disjoint.to_vec(); + if other_selections.is_empty() { + return; } - _ => {} - }); + this.selections.change_with(cx, |selections| { + selections.select_anchors(other_selections); + }); + } + }); - let this_subscription = - cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt { - EditorEvent::SelectionsChanged { local: true } => { - let these_selections = this.selections.disjoint.to_vec(); - if these_selections.is_empty() { - return; - } - other.update(cx, |other_editor, cx| { - other_editor.selections.change_with(cx, |selections| { - selections.select_anchors(these_selections); - }) - }); + let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| { + if let EditorEvent::SelectionsChanged { local: true } = this_evt { + let these_selections = this.selections.disjoint.to_vec(); + if these_selections.is_empty() { + return; } - _ => {} - }); + other.update(cx, |other_editor, cx| { + other_editor.selections.change_with(cx, |selections| { + selections.select_anchors(these_selections); + }) + }); + } + }); Subscription::join(other_subscription, this_subscription) } @@ -3342,9 +3324,9 @@ impl Editor { let old_cursor_position = &state.old_cursor_position; - self.selections_did_change(true, &old_cursor_position, state.effects, window, cx); + self.selections_did_change(true, old_cursor_position, state.effects, window, cx); - if self.should_open_signature_help_automatically(&old_cursor_position, cx) { + if self.should_open_signature_help_automatically(old_cursor_position, cx) { self.show_signature_help(&ShowSignatureHelp, window, cx); } } @@ -3764,9 +3746,9 @@ impl Editor { ColumnarSelectionState::FromMouse { selection_tail, display_point, - } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)), + } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)), ColumnarSelectionState::FromSelection { selection_tail } => { - selection_tail.to_display_point(&display_map) + selection_tail.to_display_point(display_map) } }; @@ -4043,18 +4025,18 @@ impl Editor { let following_text_allows_autoclose = snapshot .chars_at(selection.start) .next() - .map_or(true, |c| scope.should_autoclose_before(c)); + .is_none_or(|c| scope.should_autoclose_before(c)); let preceding_text_allows_autoclose = selection.start.column == 0 - || snapshot.reversed_chars_at(selection.start).next().map_or( - true, - |c| { + || snapshot + .reversed_chars_at(selection.start) + .next() + .is_none_or(|c| { bracket_pair.start != bracket_pair.end || !snapshot .char_classifier_at(selection.start) .is_word(c) - }, - ); + }); let is_closing_quote = if bracket_pair.end == bracket_pair.start && bracket_pair.start.len() == 1 @@ -4154,42 +4136,38 @@ impl Editor { if self.auto_replace_emoji_shortcode && selection.is_empty() && text.as_ref().ends_with(':') - { - if let Some(possible_emoji_short_code) = + && let Some(possible_emoji_short_code) = Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start) - { - if !possible_emoji_short_code.is_empty() { - if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) { - let emoji_shortcode_start = Point::new( - selection.start.row, - selection.start.column - possible_emoji_short_code.len() as u32 - 1, - ); + && !possible_emoji_short_code.is_empty() + && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) + { + let emoji_shortcode_start = Point::new( + selection.start.row, + selection.start.column - possible_emoji_short_code.len() as u32 - 1, + ); - // Remove shortcode from buffer - edits.push(( - emoji_shortcode_start..selection.start, - "".to_string().into(), - )); - new_selections.push(( - Selection { - id: selection.id, - start: snapshot.anchor_after(emoji_shortcode_start), - end: snapshot.anchor_before(selection.start), - reversed: selection.reversed, - goal: selection.goal, - }, - 0, - )); + // Remove shortcode from buffer + edits.push(( + emoji_shortcode_start..selection.start, + "".to_string().into(), + )); + new_selections.push(( + Selection { + id: selection.id, + start: snapshot.anchor_after(emoji_shortcode_start), + end: snapshot.anchor_before(selection.start), + reversed: selection.reversed, + goal: selection.goal, + }, + 0, + )); - // Insert emoji - let selection_start_anchor = snapshot.anchor_after(selection.start); - new_selections.push((selection.map(|_| selection_start_anchor), 0)); - edits.push((selection.start..selection.end, emoji.to_string().into())); + // Insert emoji + let selection_start_anchor = snapshot.anchor_after(selection.start); + new_selections.push((selection.map(|_| selection_start_anchor), 0)); + edits.push((selection.start..selection.end, emoji.to_string().into())); - continue; - } - } - } + continue; } // If not handling any auto-close operation, then just replace the selected @@ -4199,7 +4177,7 @@ impl Editor { if !self.linked_edit_ranges.is_empty() { let start_anchor = snapshot.anchor_before(selection.start); - let is_word_char = text.chars().next().map_or(true, |char| { + let is_word_char = text.chars().next().is_none_or(|char| { let classifier = snapshot .char_classifier_at(start_anchor.to_offset(&snapshot)) .ignore_punctuation(true); @@ -4303,12 +4281,11 @@ impl Editor { |s| s.select(new_selections), ); - if !bracket_inserted { - if let Some(on_type_format_task) = + if !bracket_inserted + && let Some(on_type_format_task) = this.trigger_on_type_formatting(text.to_string(), window, cx) - { - on_type_format_task.detach_and_log_err(cx); - } + { + on_type_format_task.detach_and_log_err(cx); } let editor_settings = EditorSettings::get_global(cx); @@ -5274,10 +5251,10 @@ impl Editor { } let language = buffer.language()?; - if let Some(restrict_to_languages) = restrict_to_languages { - if !restrict_to_languages.contains(language) { - return None; - } + if let Some(restrict_to_languages) = restrict_to_languages + && !restrict_to_languages.contains(language) + { + return None; } Some(( excerpt_id, @@ -5442,11 +5419,11 @@ impl Editor { let sort_completions = provider .as_ref() - .map_or(false, |provider| provider.sort_completions()); + .is_some_and(|provider| provider.sort_completions()); let filter_completions = provider .as_ref() - .map_or(true, |provider| provider.filter_completions()); + .is_none_or(|provider| provider.filter_completions()); let trigger_kind = match trigger { Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => { @@ -5552,7 +5529,7 @@ impl Editor { let skip_digits = query .as_ref() - .map_or(true, |query| !query.chars().any(|c| c.is_digit(10))); + .is_none_or(|query| !query.chars().any(|c| c.is_digit(10))); let (mut words, provider_responses) = match &provider { Some(provider) => { @@ -5605,15 +5582,15 @@ impl Editor { // that having one source with `is_incomplete: true` doesn't cause all to be re-queried. let mut completions = Vec::new(); let mut is_incomplete = false; - if let Some(provider_responses) = provider_responses.await.log_err() { - if !provider_responses.is_empty() { - for response in provider_responses { - completions.extend(response.completions); - is_incomplete = is_incomplete || response.is_incomplete; - } - if completion_settings.words == WordsCompletionMode::Fallback { - words = Task::ready(BTreeMap::default()); - } + if let Some(provider_responses) = provider_responses.await.log_err() + && !provider_responses.is_empty() + { + for response in provider_responses { + completions.extend(response.completions); + is_incomplete = is_incomplete || response.is_incomplete; + } + if completion_settings.words == WordsCompletionMode::Fallback { + words = Task::ready(BTreeMap::default()); } } @@ -5678,34 +5655,31 @@ impl Editor { let Ok(()) = editor.update_in(cx, |editor, window, cx| { // Newer menu already set, so exit. - match editor.context_menu.borrow().as_ref() { - Some(CodeContextMenu::Completions(prev_menu)) => { - if prev_menu.id > id { - return; - } - } - _ => {} + if let Some(CodeContextMenu::Completions(prev_menu)) = + editor.context_menu.borrow().as_ref() + && prev_menu.id > id + { + return; }; // Only valid to take prev_menu because it the new menu is immediately set // below, or the menu is hidden. - match editor.context_menu.borrow_mut().take() { - Some(CodeContextMenu::Completions(prev_menu)) => { - let position_matches = - if prev_menu.initial_position == menu.initial_position { - true - } else { - let snapshot = editor.buffer.read(cx).read(cx); - prev_menu.initial_position.to_offset(&snapshot) - == menu.initial_position.to_offset(&snapshot) - }; - if position_matches { - // Preserve markdown cache before `set_filter_results` because it will - // try to populate the documentation cache. - menu.preserve_markdown_cache(prev_menu); - } + if let Some(CodeContextMenu::Completions(prev_menu)) = + editor.context_menu.borrow_mut().take() + { + let position_matches = + if prev_menu.initial_position == menu.initial_position { + true + } else { + let snapshot = editor.buffer.read(cx).read(cx); + prev_menu.initial_position.to_offset(&snapshot) + == menu.initial_position.to_offset(&snapshot) + }; + if position_matches { + // Preserve markdown cache before `set_filter_results` because it will + // try to populate the documentation cache. + menu.preserve_markdown_cache(prev_menu); } - _ => {} }; menu.set_filter_results(matches, provider, window, cx); @@ -5718,21 +5692,21 @@ impl Editor { editor .update_in(cx, |editor, window, cx| { - if editor.focus_handle.is_focused(window) { - if let Some(menu) = menu { - *editor.context_menu.borrow_mut() = - Some(CodeContextMenu::Completions(menu)); + if editor.focus_handle.is_focused(window) + && let Some(menu) = menu + { + *editor.context_menu.borrow_mut() = + Some(CodeContextMenu::Completions(menu)); - crate::hover_popover::hide_hover(editor, cx); - if editor.show_edit_predictions_in_menu() { - editor.update_visible_edit_prediction(window, cx); - } else { - editor.discard_edit_prediction(false, cx); - } - - cx.notify(); - return; + crate::hover_popover::hide_hover(editor, cx); + if editor.show_edit_predictions_in_menu() { + editor.update_visible_edit_prediction(window, cx); + } else { + editor.discard_edit_prediction(false, cx); } + + cx.notify(); + return; } if editor.completion_tasks.len() <= 1 { @@ -5986,7 +5960,7 @@ impl Editor { let show_new_completions_on_confirm = completion .confirm .as_ref() - .map_or(false, |confirm| confirm(intent, window, cx)); + .is_some_and(|confirm| confirm(intent, window, cx)); if show_new_completions_on_confirm { self.show_completions(&ShowCompletions { trigger: None }, window, cx); } @@ -6079,11 +6053,11 @@ impl Editor { Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())), _ => { let mut task_context_task = Task::ready(None); - if let Some(tasks) = &tasks { - if let Some(project) = project { - task_context_task = - Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx); - } + if let Some(tasks) = &tasks + && let Some(project) = project + { + task_context_task = + Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx); } cx.spawn_in(window, { @@ -6118,10 +6092,10 @@ impl Editor { let spawn_straight_away = quick_launch && resolved_tasks .as_ref() - .map_or(false, |tasks| tasks.templates.len() == 1) + .is_some_and(|tasks| tasks.templates.len() == 1) && code_actions .as_ref() - .map_or(true, |actions| actions.is_empty()) + .is_none_or(|actions| actions.is_empty()) && debug_scenarios.is_empty(); editor.update_in(cx, |editor, window, cx| { @@ -6148,14 +6122,14 @@ impl Editor { deployed_from, })); cx.notify(); - if spawn_straight_away { - if let Some(task) = editor.confirm_code_action( + if spawn_straight_away + && let Some(task) = editor.confirm_code_action( &ConfirmCodeAction { item_ix: Some(0) }, window, cx, - ) { - return task; - } + ) + { + return task; } Task::ready(Ok(())) @@ -6196,12 +6170,11 @@ impl Editor { } }); Some(cx.background_spawn(async move { - let scenarios = futures::future::join_all(scenarios) + futures::future::join_all(scenarios) .await .into_iter() .flatten() - .collect::<Vec<_>>(); - scenarios + .collect::<Vec<_>>() })) }) .unwrap_or_else(|| Task::ready(vec![])) @@ -6342,21 +6315,20 @@ impl Editor { .read(cx) .excerpt_containing(editor.selections.newest_anchor().head(), cx) })?; - if let Some((_, excerpted_buffer, excerpt_range)) = excerpt { - if excerpted_buffer == *buffer { - let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| { - let excerpt_range = excerpt_range.to_offset(buffer); - buffer - .edited_ranges_for_transaction::<usize>(transaction) - .all(|range| { - excerpt_range.start <= range.start - && excerpt_range.end >= range.end - }) - })?; + if let Some((_, excerpted_buffer, excerpt_range)) = excerpt + && excerpted_buffer == *buffer + { + let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| { + let excerpt_range = excerpt_range.to_offset(buffer); + buffer + .edited_ranges_for_transaction::<usize>(transaction) + .all(|range| { + excerpt_range.start <= range.start && excerpt_range.end >= range.end + }) + })?; - if all_edits_within_excerpt { - return Ok(()); - } + if all_edits_within_excerpt { + return Ok(()); } } } @@ -6736,9 +6708,9 @@ impl Editor { let buffer_id = cursor_position.buffer_id; let buffer = this.buffer.read(cx); - if !buffer + if buffer .text_anchor_for_position(cursor_position, cx) - .map_or(false, |(buffer, _)| buffer == cursor_buffer) + .is_none_or(|(buffer, _)| buffer != cursor_buffer) { return; } @@ -6864,7 +6836,7 @@ impl Editor { for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges { match_ranges.extend( regex - .search(&buffer_snapshot, Some(search_range.clone())) + .search(buffer_snapshot, Some(search_range.clone())) .await .into_iter() .filter_map(|match_range| { @@ -6988,9 +6960,7 @@ impl Editor { || self .quick_selection_highlight_task .as_ref() - .map_or(true, |(prev_anchor_range, _)| { - prev_anchor_range != &query_range - }) + .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range) { let multi_buffer_visible_start = self .scroll_manager @@ -7019,9 +6989,7 @@ impl Editor { || self .debounced_selection_highlight_task .as_ref() - .map_or(true, |(prev_anchor_range, _)| { - prev_anchor_range != &query_range - }) + .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range) { let multi_buffer_start = multi_buffer_snapshot .anchor_before(0) @@ -7156,9 +7124,7 @@ impl Editor { && self .edit_prediction_provider .as_ref() - .map_or(false, |provider| { - provider.provider.show_completions_in_menu() - }); + .is_some_and(|provider| provider.provider.show_completions_in_menu()); let preview_requires_modifier = all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle; @@ -7206,7 +7172,7 @@ impl Editor { return Some(false); } let provider = self.edit_prediction_provider()?; - if !provider.is_enabled(&buffer, buffer_position, cx) { + if !provider.is_enabled(buffer, buffer_position, cx) { return Some(false); } let buffer = buffer.read(cx); @@ -7742,7 +7708,7 @@ impl Editor { || self .active_edit_prediction .as_ref() - .map_or(false, |completion| { + .is_some_and(|completion| { let invalidation_range = completion.invalidation_range.to_offset(&multibuffer); let invalidation_range = invalidation_range.start..=invalidation_range.end; !invalidation_range.contains(&offset_selection.head()) @@ -7764,6 +7730,11 @@ impl Editor { self.edit_prediction_settings = self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx); + if let EditPredictionSettings::Disabled = self.edit_prediction_settings { + self.discard_edit_prediction(false, cx); + return None; + }; + self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor); if self.edit_prediction_indent_conflict { @@ -7771,10 +7742,10 @@ impl Editor { let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx); - if let Some((_, indent)) = indents.iter().next() { - if indent.len == cursor_point.column { - self.edit_prediction_indent_conflict = false; - } + if let Some((_, indent)) = indents.iter().next() + && indent.len == cursor_point.column + { + self.edit_prediction_indent_conflict = false; } } @@ -7966,7 +7937,7 @@ impl Editor { let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position); let position = multi_buffer_anchor - .to_point(&multi_buffer_snapshot) + .to_point(multi_buffer_snapshot) .to_display_point(&snapshot); breakpoint_display_points.insert( @@ -8435,7 +8406,7 @@ impl Editor { .context_menu .borrow() .as_ref() - .map_or(false, |menu| menu.visible()) + .is_some_and(|menu| menu.visible()) } pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> { @@ -8859,7 +8830,7 @@ impl Editor { } let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() { - crate::edit_prediction_edit_text(&snapshot, edits, edit_preview, false, cx) + crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx) } else { // Fallback for providers without edit_preview crate::edit_prediction_fallback_text(edits, cx) @@ -8981,9 +8952,8 @@ impl Editor { let end_row = start_row + line_count as u32; visible_row_range.contains(&start_row) && visible_row_range.contains(&end_row) - && cursor_row.map_or(true, |cursor_row| { - !((start_row..end_row).contains(&cursor_row)) - }) + && cursor_row + .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row))) })?; content_origin @@ -9222,7 +9192,7 @@ impl Editor { .child(div().px_1p5().child(match &prediction.completion { EditPrediction::Move { target, snapshot } => { use text::ToPoint as _; - if target.text_anchor.to_point(&snapshot).row > cursor_point.row + if target.text_anchor.to_point(snapshot).row > cursor_point.row { Icon::new(IconName::ZedPredictDown) } else { @@ -9424,7 +9394,7 @@ impl Editor { .gap_2() .flex_1() .child( - if target.text_anchor.to_point(&snapshot).row > cursor_point.row { + if target.text_anchor.to_point(snapshot).row > cursor_point.row { Icon::new(IconName::ZedPredictDown) } else { Icon::new(IconName::ZedPredictUp) @@ -9440,14 +9410,14 @@ impl Editor { snapshot, display_mode: _, } => { - let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row; + let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row; let (highlighted_edits, has_more_lines) = if let Some(edit_preview) = edit_preview.as_ref() { - crate::edit_prediction_edit_text(&snapshot, &edits, edit_preview, true, cx) + crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx) .first_line_preview() } else { - crate::edit_prediction_fallback_text(&edits, cx).first_line_preview() + crate::edit_prediction_fallback_text(edits, cx).first_line_preview() }; let styled_text = gpui::StyledText::new(highlighted_edits.text) @@ -9523,10 +9493,10 @@ impl Editor { let context_menu = self.context_menu.borrow_mut().take(); self.stale_edit_prediction_in_menu.take(); self.update_visible_edit_prediction(window, cx); - if let Some(CodeContextMenu::Completions(_)) = &context_menu { - if let Some(completion_provider) = &self.completion_provider { - completion_provider.selection_changed(None, window, cx); - } + if let Some(CodeContextMenu::Completions(_)) = &context_menu + && let Some(completion_provider) = &self.completion_provider + { + completion_provider.selection_changed(None, window, cx); } context_menu } @@ -9593,7 +9563,7 @@ impl Editor { .tabstops .iter() .map(|tabstop| { - let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| { + let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| { tabstop.is_empty() && tabstop.start == snippet.text.len() as isize }); let mut tabstop_ranges = tabstop @@ -9631,10 +9601,10 @@ impl Editor { s.select_ranges(tabstop.ranges.iter().rev().cloned()); }); - if let Some(choices) = &tabstop.choices { - if let Some(selection) = tabstop.ranges.first() { - self.show_snippet_choices(choices, selection.clone(), cx) - } + if let Some(choices) = &tabstop.choices + && let Some(selection) = tabstop.ranges.first() + { + self.show_snippet_choices(choices, selection.clone(), cx) } // If we're already at the last tabstop and it's at the end of the snippet, @@ -9768,10 +9738,10 @@ impl Editor { s.select_ranges(current_ranges.iter().rev().cloned()) }); - if let Some(choices) = &snippet.choices[snippet.active_index] { - if let Some(selection) = current_ranges.first() { - self.show_snippet_choices(&choices, selection.clone(), cx); - } + if let Some(choices) = &snippet.choices[snippet.active_index] + && let Some(selection) = current_ranges.first() + { + self.show_snippet_choices(choices, selection.clone(), cx); } // If snippet state is not at the last tabstop, push it back on the stack @@ -10168,10 +10138,10 @@ impl Editor { // Avoid re-outdenting a row that has already been outdented by a // previous selection. - if let Some(last_row) = last_outdent { - if last_row == rows.start { - rows.start = rows.start.next_row(); - } + if let Some(last_row) = last_outdent + && last_row == rows.start + { + rows.start = rows.start.next_row(); } let has_multiple_rows = rows.len() > 1; for row in rows.iter_rows() { @@ -10349,11 +10319,11 @@ impl Editor { MultiBufferRow(selection.end.row) }; - if let Some(last_row_range) = row_ranges.last_mut() { - if start <= last_row_range.end { - last_row_range.end = end; - continue; - } + if let Some(last_row_range) = row_ranges.last_mut() + && start <= last_row_range.end + { + last_row_range.end = end; + continue; } row_ranges.push(start..end); } @@ -10655,8 +10625,7 @@ impl Editor { .buffer_snapshot .anchor_after(Point::new(row, line_len)); - let bp = self - .breakpoint_store + self.breakpoint_store .as_ref()? .read_with(cx, |breakpoint_store, cx| { breakpoint_store @@ -10681,8 +10650,7 @@ impl Editor { None } }) - }); - bp + }) } pub fn edit_log_breakpoint( @@ -10718,7 +10686,7 @@ impl Editor { let cursors = self .selections .disjoint_anchors() - .into_iter() + .iter() .map(|selection| { let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot); @@ -11724,7 +11692,7 @@ impl Editor { let transpose_start = display_map .buffer_snapshot .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); - if edits.last().map_or(true, |e| e.0.end <= transpose_start) { + if edits.last().is_none_or(|e| e.0.end <= transpose_start) { let transpose_end = display_map .buffer_snapshot .clip_offset(transpose_offset + 1, Bias::Right); @@ -12284,7 +12252,7 @@ impl Editor { let trigger_in_words = this.show_edit_predictions_in_menu() || !had_active_edit_prediction; - this.trigger_completion_on_input(&text, trigger_in_words, window, cx); + this.trigger_completion_on_input(text, trigger_in_words, window, cx); }); } @@ -14834,15 +14802,18 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let buffer = self.buffer.read(cx).snapshot(cx); - let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into(); + let selections = self + .selections + .all::<usize>(cx) + .into_iter() + // subtracting the offset requires sorting + .sorted_by_key(|i| i.start); - let edits = old_selections - .iter() - // only consider the first selection for now - .take(1) - .map(|selection| { + let full_edits = selections + .into_iter() + .filter_map(|selection| { // Only requires two branches once if-let-chains stabilize (#53667) - let selection_range = if !selection.is_empty() { + let child = if !selection.is_empty() { selection.range() } else if let Some((_, ancestor_range)) = buffer.syntax_ancestor(selection.start..selection.end) @@ -14855,48 +14826,52 @@ impl Editor { selection.range() }; - let mut new_range = selection_range.clone(); - while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) { - new_range = match ancestor_range { + let mut parent = child.clone(); + while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) { + parent = match ancestor_range { MultiOrSingleBufferOffsetRange::Single(range) => range, MultiOrSingleBufferOffsetRange::Multi(range) => range, }; - if new_range.start < selection_range.start - || new_range.end > selection_range.end - { + if parent.start < child.start || parent.end > child.end { break; } } - (selection, selection_range, new_range) + if parent == child { + return None; + } + let text = buffer.text_for_range(child.clone()).collect::<String>(); + Some((selection.id, parent, text)) }) .collect::<Vec<_>>(); - self.transact(window, cx, |editor, window, cx| { - for (_, child, parent) in &edits { - let text = buffer.text_for_range(child.clone()).collect::<String>(); - editor.replace_text_in_range(Some(parent.clone()), &text, window, cx); - } - - editor.change_selections( - SelectionEffects::scroll(Autoscroll::fit()), - window, - cx, - |s| { - s.select( - edits - .iter() - .map(|(s, old, new)| Selection { - id: s.id, - start: new.start, - end: new.start + old.len(), - goal: SelectionGoal::None, - reversed: s.reversed, - }) - .collect(), - ); - }, - ); + self.transact(window, cx, |this, window, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit( + full_edits + .iter() + .map(|(_, p, t)| (p.clone(), t.clone())) + .collect::<Vec<_>>(), + None, + cx, + ); + }); + this.change_selections(Default::default(), window, cx, |s| { + let mut offset = 0; + let mut selections = vec![]; + for (id, parent, text) in full_edits { + let start = parent.start - offset; + offset += parent.len() - text.len(); + selections.push(Selection { + id, + start, + end: start + text.len(), + reversed: false, + goal: Default::default(), + }); + } + s.select(selections); + }); }); } @@ -14920,10 +14895,7 @@ impl Editor { }; let hide_runnables = project - .update(cx, |project, cx| { - // Do not display any test indicators in non-dev server remote projects. - project.is_via_collab() && project.ssh_connection_string(cx).is_none() - }) + .update(cx, |project, _| project.is_via_collab()) .unwrap_or(true); if hide_runnables { return; @@ -15316,17 +15288,15 @@ impl Editor { if direction == ExpandExcerptDirection::Down { let multi_buffer = self.buffer.read(cx); let snapshot = multi_buffer.snapshot(cx); - if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) { - if let Some(buffer) = multi_buffer.buffer(buffer_id) { - if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) { - let buffer_snapshot = buffer.read(cx).snapshot(); - let excerpt_end_row = - Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row; - let last_row = buffer_snapshot.max_point().row; - let lines_below = last_row.saturating_sub(excerpt_end_row); - should_scroll_up = lines_below >= lines_to_expand; - } - } + if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) + && let Some(buffer) = multi_buffer.buffer(buffer_id) + && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) + { + let buffer_snapshot = buffer.read(cx).snapshot(); + let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row; + let last_row = buffer_snapshot.max_point().row; + let lines_below = last_row.saturating_sub(excerpt_end_row); + should_scroll_up = lines_below >= lines_to_expand; } } @@ -15411,10 +15381,10 @@ impl Editor { let selection = self.selections.newest::<usize>(cx); let mut active_group_id = None; - if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics { - if active_group.active_range.start.to_offset(&buffer) == selection.start { - active_group_id = Some(active_group.group_id); - } + if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics + && active_group.active_range.start.to_offset(&buffer) == selection.start + { + active_group_id = Some(active_group.group_id); } fn filtered( @@ -16232,23 +16202,21 @@ impl Editor { if split { workspace.split_item(SplitDirection::Right, item.clone(), 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()) - }); + } 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.clone(), preview_item_idx, true, window, cx); + workspace.add_item_to_active_pane(item.clone(), 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); - }); - } - } else { - workspace.add_item_to_active_pane(item.clone(), None, 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); + }); } + } else { + workspace.add_item_to_active_pane(item.clone(), None, true, window, cx); } workspace.active_pane().update(cx, |pane, cx| { pane.set_preview_item_id(Some(item_id), cx); @@ -16638,10 +16606,7 @@ impl Editor { .transaction(transaction_id_prev) .map(|t| t.0.clone()) }) - .unwrap_or_else(|| { - log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated"); - self.selections.disjoint_anchors() - }); + .unwrap_or_else(|| self.selections.disjoint_anchors()); let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse(); let format = project.update(cx, |project, cx| { @@ -16659,10 +16624,10 @@ impl Editor { buffer .update(cx, |buffer, cx| { - if let Some(transaction) = transaction { - if !buffer.is_singleton() { - buffer.push_transaction(&transaction.0, cx); - } + if let Some(transaction) = transaction + && !buffer.is_singleton() + { + buffer.push_transaction(&transaction.0, cx); } cx.notify(); }) @@ -16728,10 +16693,10 @@ impl Editor { buffer .update(cx, |buffer, cx| { // check if we need this - if let Some(transaction) = transaction { - if !buffer.is_singleton() { - buffer.push_transaction(&transaction.0, cx); - } + if let Some(transaction) = transaction + && !buffer.is_singleton() + { + buffer.push_transaction(&transaction.0, cx); } cx.notify(); }) @@ -17363,12 +17328,12 @@ impl Editor { } for row in (0..=range.start.row).rev() { - if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) { - if crease.range().end.row >= buffer_start_row { - to_fold.push(crease); - if row <= range.start.row { - break; - } + if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) + && crease.range().end.row >= buffer_start_row + { + to_fold.push(crease); + if row <= range.start.row { + break; } } } @@ -17896,7 +17861,7 @@ impl Editor { ranges: &[Range<Anchor>], snapshot: &MultiBufferSnapshot, ) -> bool { - let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot); + let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot); hunks.any(|hunk| hunk.status().has_secondary_hunk()) } @@ -18678,10 +18643,10 @@ impl Editor { pub fn working_directory(&self, cx: &App) -> Option<PathBuf> { if let Some(buffer) = self.buffer().read(cx).as_singleton() { - if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { - if let Some(dir) = file.abs_path(cx).parent() { - return Some(dir.to_owned()); - } + if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) + && let Some(dir) = file.abs_path(cx).parent() + { + return Some(dir.to_owned()); } if let Some(project_path) = buffer.read(cx).project_path(cx) { @@ -18741,10 +18706,10 @@ impl Editor { _window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(path) = self.target_file_abs_path(cx) { - if let Some(path) = path.to_str() { - cx.write_to_clipboard(ClipboardItem::new_string(path.to_string())); - } + if let Some(path) = self.target_file_abs_path(cx) + && let Some(path) = path.to_str() + { + cx.write_to_clipboard(ClipboardItem::new_string(path.to_string())); } } @@ -18754,10 +18719,10 @@ impl Editor { _window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(path) = self.target_file_path(cx) { - if let Some(path) = path.to_str() { - cx.write_to_clipboard(ClipboardItem::new_string(path.to_string())); - } + if let Some(path) = self.target_file_path(cx) + && let Some(path) = path.to_str() + { + cx.write_to_clipboard(ClipboardItem::new_string(path.to_string())); } } @@ -18826,22 +18791,20 @@ impl Editor { _: &mut Window, cx: &mut Context<Self>, ) { - if let Some(file) = self.target_file(cx) { - if let Some(file_stem) = file.path().file_stem() { - if let Some(name) = file_stem.to_str() { - cx.write_to_clipboard(ClipboardItem::new_string(name.to_string())); - } - } + if let Some(file) = self.target_file(cx) + && let Some(file_stem) = file.path().file_stem() + && let Some(name) = file_stem.to_str() + { + cx.write_to_clipboard(ClipboardItem::new_string(name.to_string())); } } pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) { - if let Some(file) = self.target_file(cx) { - if let Some(file_name) = file.path().file_name() { - if let Some(name) = file_name.to_str() { - cx.write_to_clipboard(ClipboardItem::new_string(name.to_string())); - } - } + if let Some(file) = self.target_file(cx) + && let Some(file_name) = file.path().file_name() + && let Some(name) = file_name.to_str() + { + cx.write_to_clipboard(ClipboardItem::new_string(name.to_string())); } } @@ -19015,7 +18978,7 @@ impl Editor { fn has_blame_entries(&self, cx: &App) -> bool { self.blame() - .map_or(false, |blame| blame.read(cx).has_generated_entries()) + .is_some_and(|blame| blame.read(cx).has_generated_entries()) } fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool { @@ -19042,8 +19005,8 @@ impl Editor { buffer_ranges.last() }?; - let selection = text::ToPoint::to_point(&range.start, &buffer).row - ..text::ToPoint::to_point(&range.end, &buffer).row; + let selection = text::ToPoint::to_point(&range.start, buffer).row + ..text::ToPoint::to_point(&range.end, buffer).row; Some(( multi_buffer.buffer(buffer.remote_id()).unwrap().clone(), selection, @@ -19111,10 +19074,10 @@ impl Editor { cx: &mut Context<Self>, ) { let selection = self.selections.newest::<Point>(cx).start.row + 1; - if let Some(file) = self.target_file(cx) { - if let Some(path) = file.path().to_str() { - cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}"))); - } + if let Some(file) = self.target_file(cx) + && let Some(path) = file.path().to_str() + { + cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}"))); } } @@ -19218,7 +19181,7 @@ impl Editor { let locations = self .selections .all_anchors(cx) - .into_iter() + .iter() .map(|selection| Location { buffer: buffer.clone(), range: selection.start.text_anchor..selection.end.text_anchor, @@ -19665,7 +19628,7 @@ impl Editor { pub fn has_background_highlights<T: 'static>(&self) -> bool { self.background_highlights .get(&HighlightKey::Type(TypeId::of::<T>())) - .map_or(false, |(_, highlights)| !highlights.is_empty()) + .is_some_and(|(_, highlights)| !highlights.is_empty()) } pub fn background_highlights_in_range( @@ -19754,10 +19717,10 @@ impl Editor { break; } let end = range.end.to_point(&display_snapshot.buffer_snapshot); - if let Some(current_row) = &end_row { - if end.row == current_row.row { - continue; - } + if let Some(current_row) = &end_row + && end.row == current_row.row + { + continue; } let start = range.start.to_point(&display_snapshot.buffer_snapshot); if start_row.is_none() { @@ -19930,11 +19893,8 @@ impl Editor { event: &SessionEvent, cx: &mut Context<Self>, ) { - match event { - SessionEvent::InvalidateInlineValue => { - self.refresh_inline_values(cx); - } - _ => {} + if let SessionEvent::InvalidateInlineValue = event { + self.refresh_inline_values(cx); } } @@ -20049,17 +20009,16 @@ impl Editor { if self.has_active_edit_prediction() { self.update_visible_edit_prediction(window, cx); } - if let Some(project) = self.project.as_ref() { - if let Some(edited_buffer) = edited_buffer { - project.update(cx, |project, cx| { - self.registered_buffers - .entry(edited_buffer.read(cx).remote_id()) - .or_insert_with(|| { - project - .register_buffer_with_language_servers(&edited_buffer, cx) - }); - }); - } + if let Some(project) = self.project.as_ref() + && let Some(edited_buffer) = edited_buffer + { + project.update(cx, |project, cx| { + self.registered_buffers + .entry(edited_buffer.read(cx).remote_id()) + .or_insert_with(|| { + project.register_buffer_with_language_servers(edited_buffer, cx) + }); + }); } cx.emit(EditorEvent::BufferEdited); cx.emit(SearchEvent::MatchesInvalidated); @@ -20069,10 +20028,10 @@ impl Editor { } if *singleton_buffer_edited { - if let Some(buffer) = edited_buffer { - if buffer.read(cx).file().is_none() { - cx.emit(EditorEvent::TitleChanged); - } + if let Some(buffer) = edited_buffer + && buffer.read(cx).file().is_none() + { + cx.emit(EditorEvent::TitleChanged); } if let Some(project) = &self.project { #[allow(clippy::mutable_key_type)] @@ -20118,17 +20077,17 @@ impl Editor { } => { self.tasks_update_task = Some(self.refresh_runnables(window, cx)); let buffer_id = buffer.read(cx).remote_id(); - if self.buffer.read(cx).diff_for(buffer_id).is_none() { - if let Some(project) = &self.project { - update_uncommitted_diff_for_buffer( - cx.entity(), - project, - [buffer.clone()], - self.buffer.clone(), - cx, - ) - .detach(); - } + if self.buffer.read(cx).diff_for(buffer_id).is_none() + && let Some(project) = &self.project + { + update_uncommitted_diff_for_buffer( + cx.entity(), + project, + [buffer.clone()], + self.buffer.clone(), + cx, + ) + .detach(); } self.update_lsp_data(false, Some(buffer_id), window, cx); cx.emit(EditorEvent::ExcerptsAdded { @@ -20588,7 +20547,7 @@ impl Editor { // For now, don't allow opening excerpts in buffers that aren't backed by // regular project files. fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool { - file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some()) + file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some()) } fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> { @@ -20732,11 +20691,11 @@ impl Editor { let mut chunk_lines = chunk.text.split('\n').peekable(); while let Some(text) = chunk_lines.next() { let mut merged_with_last_token = false; - if let Some(last_token) = line.back_mut() { - if last_token.highlight == highlight { - last_token.text.push_str(text); - merged_with_last_token = true; - } + if let Some(last_token) = line.back_mut() + && last_token.highlight == highlight + { + last_token.text.push_str(text); + merged_with_last_token = true; } if !merged_with_last_token { @@ -20926,7 +20885,7 @@ impl Editor { let existing_pending = self .text_highlights::<PendingInput>(cx) - .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>()); + .map(|(_, ranges)| ranges.to_vec()); if existing_pending.is_none() && pending.is_empty() { return; } @@ -21079,7 +21038,7 @@ impl Editor { }; if let Some((workspace, path)) = workspace.as_ref().zip(path) { let Some(task) = cx - .update_window_entity(&workspace, |workspace, window, cx| { + .update_window_entity(workspace, |workspace, window, cx| { workspace .open_path_preview(path, None, false, false, false, window, cx) }) @@ -21131,7 +21090,7 @@ impl Editor { pub fn has_visible_completions_menu(&self) -> bool { !self.edit_prediction_preview_is_active() - && self.context_menu.borrow().as_ref().map_or(false, |menu| { + && self.context_menu.borrow().as_ref().is_some_and(|menu| { menu.visible() && matches!(menu, CodeContextMenu::Completions(_)) }) } @@ -21195,39 +21154,37 @@ impl Editor { { let buffer_snapshot = OnceCell::new(); - if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() { - if !folds.is_empty() { - let snapshot = - buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx)); - self.fold_ranges( - folds - .into_iter() - .map(|(start, end)| { - snapshot.clip_offset(start, Bias::Left) - ..snapshot.clip_offset(end, Bias::Right) - }) - .collect(), - false, - window, - cx, - ); - } - } - - if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() { - if !selections.is_empty() { - let snapshot = - buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx)); - // skip adding the initial selection to selection history - self.selection_history.mode = SelectionHistoryMode::Skipping; - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges(selections.into_iter().map(|(start, end)| { + if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() + && !folds.is_empty() + { + let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx)); + self.fold_ranges( + folds + .into_iter() + .map(|(start, end)| { snapshot.clip_offset(start, Bias::Left) ..snapshot.clip_offset(end, Bias::Right) - })); - }); - self.selection_history.mode = SelectionHistoryMode::Normal; - } + }) + .collect(), + false, + window, + cx, + ); + } + + if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() + && !selections.is_empty() + { + let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx)); + // skip adding the initial selection to selection history + self.selection_history.mode = SelectionHistoryMode::Skipping; + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(selections.into_iter().map(|(start, end)| { + snapshot.clip_offset(start, Bias::Left) + ..snapshot.clip_offset(end, Bias::Right) + })); + }); + self.selection_history.mode = SelectionHistoryMode::Normal; }; } @@ -21269,17 +21226,15 @@ fn process_completion_for_edit( let mut snippet_source = completion.new_text.clone(); let mut previous_point = text::ToPoint::to_point(cursor_position, buffer); previous_point.column = previous_point.column.saturating_sub(1); - if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) { - if scope.prefers_label_for_snippet_in_completion() { - if let Some(label) = completion.label() { - if matches!( - completion.kind(), - Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD) - ) { - snippet_source = label; - } - } - } + if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) + && scope.prefers_label_for_snippet_in_completion() + && let Some(label) = completion.label() + && matches!( + completion.kind(), + Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD) + ) + { + snippet_source = label; } match Snippet::parse(&snippet_source).log_err() { Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text), @@ -21303,14 +21258,14 @@ fn process_completion_for_edit( debug_assert!( insert_range .start - .cmp(&cursor_position, &buffer_snapshot) + .cmp(cursor_position, &buffer_snapshot) .is_le(), "insert_range should start before or at cursor position" ); debug_assert!( replace_range .start - .cmp(&cursor_position, &buffer_snapshot) + .cmp(cursor_position, &buffer_snapshot) .is_le(), "replace_range should start before or at cursor position" ); @@ -21333,10 +21288,10 @@ fn process_completion_for_edit( ); let mut current_needle = text_to_replace.next(); for haystack_ch in completion.label.text.chars() { - if let Some(needle_ch) = current_needle { - if haystack_ch.eq_ignore_ascii_case(&needle_ch) { - current_needle = text_to_replace.next(); - } + if let Some(needle_ch) = current_needle + && haystack_ch.eq_ignore_ascii_case(&needle_ch) + { + current_needle = text_to_replace.next(); } } current_needle.is_none() @@ -21344,7 +21299,7 @@ fn process_completion_for_edit( LspInsertMode::ReplaceSuffix => { if replace_range .end - .cmp(&cursor_position, &buffer_snapshot) + .cmp(cursor_position, &buffer_snapshot) .is_gt() { let range_after_cursor = *cursor_position..replace_range.end; @@ -21380,7 +21335,7 @@ fn process_completion_for_edit( if range_to_replace .end - .cmp(&cursor_position, &buffer_snapshot) + .cmp(cursor_position, &buffer_snapshot) .is_lt() { range_to_replace.end = *cursor_position; @@ -21388,7 +21343,7 @@ fn process_completion_for_edit( CompletionEdit { new_text, - replace_range: range_to_replace.to_offset(&buffer), + replace_range: range_to_replace.to_offset(buffer), snippet, } } @@ -21558,9 +21513,9 @@ fn is_grapheme_whitespace(text: &str) -> bool { } fn should_stay_with_preceding_ideograph(text: &str) -> bool { - text.chars().next().map_or(false, |ch| { - matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…') - }) + text.chars() + .next() + .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')) } #[derive(PartialEq, Eq, Debug, Clone, Copy)] @@ -21590,20 +21545,20 @@ impl<'a> Iterator for WordBreakingTokenizer<'a> { offset += first_grapheme.len(); grapheme_len += 1; if is_grapheme_ideographic(first_grapheme) && !is_whitespace { - if let Some(grapheme) = iter.peek().copied() { - if should_stay_with_preceding_ideograph(grapheme) { - offset += grapheme.len(); - grapheme_len += 1; - } + if let Some(grapheme) = iter.peek().copied() + && should_stay_with_preceding_ideograph(grapheme) + { + offset += grapheme.len(); + grapheme_len += 1; } } else { let mut words = self.input[offset..].split_word_bound_indices().peekable(); let mut next_word_bound = words.peek().copied(); - if next_word_bound.map_or(false, |(i, _)| i == 0) { + if next_word_bound.is_some_and(|(i, _)| i == 0) { next_word_bound = words.next(); } while let Some(grapheme) = iter.peek().copied() { - if next_word_bound.map_or(false, |(i, _)| i == offset) { + if next_word_bound.is_some_and(|(i, _)| i == offset) { break; }; if is_grapheme_whitespace(grapheme) != is_whitespace @@ -22137,7 +22092,7 @@ fn snippet_completions( snippet .prefix .iter() - .map(move |prefix| StringMatchCandidate::new(ix, &prefix)) + .map(move |prefix| StringMatchCandidate::new(ix, prefix)) }) .collect::<Vec<StringMatchCandidate>>(); @@ -22366,10 +22321,10 @@ impl SemanticsProvider for Entity<Project> { cx: &mut App, ) -> Option<Task<Result<Vec<LocationLink>>>> { Some(self.update(cx, |project, cx| match kind { - GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx), - GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx), - GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx), - GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx), + GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx), + GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx), + GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx), + GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx), })) } @@ -23778,7 +23733,7 @@ fn all_edits_insertions_or_deletions( let mut all_deletions = true; for (range, new_text) in edits.iter() { - let range_is_empty = range.to_offset(&snapshot).is_empty(); + let range_is_empty = range.to_offset(snapshot).is_empty(); let text_is_empty = new_text.is_empty(); if range_is_empty != text_is_empty { diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index c635d5c8b1..b52353c86d 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -800,10 +800,8 @@ impl Settings for EditorSettings { if gutter.line_numbers.is_some() { old_gutter.line_numbers = gutter.line_numbers } - } else { - if gutter != GutterContent::default() { - current.gutter = Some(gutter) - } + } else if gutter != GutterContent::default() { + current.gutter = Some(gutter) } if let Some(b) = vscode.read_bool("editor.scrollBeyondLastLine") { current.scroll_beyond_last_line = Some(if b { diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index f97dcd712c..1f1239ba0a 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8015,7 +8015,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte } #[gpui::test] -async fn test_unwrap_syntax_node(cx: &mut gpui::TestAppContext) { +async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; @@ -8029,21 +8029,12 @@ async fn test_unwrap_syntax_node(cx: &mut gpui::TestAppContext) { buffer.set_language(Some(language), cx); }); - cx.set_state( - &r#" - use mod1::mod2::{«mod3ˇ», mod4}; - "# - .unindent(), - ); + cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# }); cx.update_editor(|editor, window, cx| { editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx); }); - cx.assert_editor_state( - &r#" - use mod1::mod2::«mod3ˇ»; - "# - .unindent(), - ); + + cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# }); } #[gpui::test] @@ -8393,7 +8384,7 @@ async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) buffer.set_language(Some(language), cx); }); - cx.set_state(&r#"struct A {ˇ}"#); + cx.set_state(r#"struct A {ˇ}"#); cx.update_editor(|editor, window, cx| { editor.newline(&Default::default(), window, cx); @@ -8405,7 +8396,7 @@ async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) }" )); - cx.set_state(&r#"select_biased!(ˇ)"#); + cx.set_state(r#"select_biased!(ˇ)"#); cx.update_editor(|editor, window, cx| { editor.newline(&Default::default(), window, cx); @@ -12319,7 +12310,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) let counter = Arc::new(AtomicUsize::new(0)); handle_completion_request_with_insert_and_replace( &mut cx, - &buffer_marked_text, + buffer_marked_text, vec![(completion_text, completion_text)], counter.clone(), ) @@ -12333,7 +12324,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) .confirm_completion_replace(&ConfirmCompletionReplace, window, cx) .unwrap() }); - cx.assert_editor_state(&expected_with_replace_mode); + cx.assert_editor_state(expected_with_replace_mode); handle_resolve_completion_request(&mut cx, None).await; apply_additional_edits.await.unwrap(); @@ -12353,7 +12344,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) }); handle_completion_request_with_insert_and_replace( &mut cx, - &buffer_marked_text, + buffer_marked_text, vec![(completion_text, completion_text)], counter.clone(), ) @@ -12367,7 +12358,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) .confirm_completion_insert(&ConfirmCompletionInsert, window, cx) .unwrap() }); - cx.assert_editor_state(&expected_with_insert_mode); + cx.assert_editor_state(expected_with_insert_mode); handle_resolve_completion_request(&mut cx, None).await; apply_additional_edits.await.unwrap(); } @@ -13141,7 +13132,7 @@ async fn test_word_completion(cx: &mut TestAppContext) { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { assert_eq!( - completion_menu_entries(&menu), + completion_menu_entries(menu), &["first", "last"], "When LSP server is fast to reply, no fallback word completions are used" ); @@ -13164,7 +13155,7 @@ async fn test_word_completion(cx: &mut TestAppContext) { cx.update_editor(|editor, _, _| { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { - assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"], + assert_eq!(completion_menu_entries(menu), &["one", "three", "two"], "When LSP server is slow, document words can be shown instead, if configured accordingly"); } else { panic!("expected completion menu to be open"); @@ -13225,7 +13216,7 @@ async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { assert_eq!( - completion_menu_entries(&menu), + completion_menu_entries(menu), &["first", "last", "second"], "Word completions that has the same edit as the any of the LSP ones, should not be proposed" ); @@ -13281,7 +13272,7 @@ async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { assert_eq!( - completion_menu_entries(&menu), + completion_menu_entries(menu), &["first", "last", "second"], "`ShowWordCompletions` action should show word completions" ); @@ -13298,7 +13289,7 @@ async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { assert_eq!( - completion_menu_entries(&menu), + completion_menu_entries(menu), &["last"], "After showing word completions, further editing should filter them and not query the LSP" ); @@ -13337,7 +13328,7 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { assert_eq!( - completion_menu_entries(&menu), + completion_menu_entries(menu), &["let"], "With no digits in the completion query, no digits should be in the word completions" ); @@ -13362,7 +13353,7 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) { cx.update_editor(|editor, _, _| { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { - assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \ + assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \ return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)"); } else { panic!("expected completion menu to be open"); @@ -13599,7 +13590,7 @@ async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) { cx.update_editor(|editor, _, _| { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { - assert_eq!(completion_menu_entries(&menu), &["first", "last"]); + assert_eq!(completion_menu_entries(menu), &["first", "last"]); } else { panic!("expected completion menu to be open"); } @@ -16702,7 +16693,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestA if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { assert_eq!( - completion_menu_entries(&menu), + completion_menu_entries(menu), &["bg-blue", "bg-red", "bg-yellow"] ); } else { @@ -16715,7 +16706,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestA cx.update_editor(|editor, _, _| { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { - assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]); + assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]); } else { panic!("expected completion menu to be open"); } @@ -16729,7 +16720,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestA cx.update_editor(|editor, _, _| { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { - assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]); + assert_eq!(completion_menu_entries(menu), &["bg-yellow"]); } else { panic!("expected completion menu to be open"); } @@ -17298,7 +17289,7 @@ async fn test_multibuffer_reverts(cx: &mut TestAppContext) { (buffer_2.clone(), base_text_2), (buffer_3.clone(), base_text_3), ] { - let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx)); editor .buffer .update(cx, |buffer, cx| buffer.add_diff(diff, cx)); @@ -17919,7 +17910,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) { (buffer_2.clone(), file_2_old), (buffer_3.clone(), file_3_old), ] { - let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx)); editor .buffer .update(cx, |buffer, cx| buffer.add_diff(diff, cx)); @@ -21024,7 +21015,7 @@ async fn assert_highlighted_edits( cx.update(|_window, cx| { let highlighted_edits = edit_prediction_edit_text( - &snapshot.as_singleton().unwrap().2, + snapshot.as_singleton().unwrap().2, &edits, &edit_preview, include_deletions, @@ -21046,7 +21037,7 @@ fn assert_breakpoint( let mut breakpoint = breakpoints .get(path) .unwrap() - .into_iter() + .iter() .map(|breakpoint| { ( breakpoint.row, @@ -21091,7 +21082,7 @@ fn add_log_breakpoint_at_cursor( .buffer_snapshot .anchor_before(Point::new(cursor_position.row, 0)); - (breakpoint_position, Breakpoint::new_log(&log_message)) + (breakpoint_position, Breakpoint::new_log(log_message)) }); editor.edit_breakpoint_at_anchor( @@ -23631,7 +23622,7 @@ pub fn handle_completion_request( complete_from_position ); Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList { - is_incomplete: is_incomplete, + is_incomplete, item_defaults: None, items: completions .iter() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 808f2f84d4..09cc0db7b8 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -82,7 +82,7 @@ use sum_tree::Bias; use text::{BufferId, SelectionGoal}; use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor}; use ui::{ - ButtonLike, ContextMenu, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*, + ButtonLike, ContextMenu, Indicator, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*, right_click_menu, scrollbars::ShowScrollbar, }; use unicode_segmentation::UnicodeSegmentation; @@ -724,7 +724,7 @@ impl EditorElement { ColumnarMode::FromMouse => true, ColumnarMode::FromSelection => false, }, - mode: mode, + mode, goal_column: point_for_position.exact_unclipped.column(), }, window, @@ -917,6 +917,11 @@ impl EditorElement { } else if cfg!(any(target_os = "linux", target_os = "freebsd")) && event.button == MouseButton::Middle { + #[allow( + clippy::collapsible_if, + clippy::needless_return, + reason = "The cfg-block below makes this a false positive" + )] if !text_hitbox.is_hovered(window) || editor.read_only(cx) { return; } @@ -1122,26 +1127,24 @@ impl EditorElement { let hovered_diff_hunk_row = if let Some(control_row) = hovered_diff_control { Some(control_row) - } else { - if text_hovered { - let current_row = valid_point.row(); - position_map.display_hunks.iter().find_map(|(hunk, _)| { - if let DisplayDiffHunk::Unfolded { - display_row_range, .. - } = hunk - { - if display_row_range.contains(¤t_row) { - Some(display_row_range.start) - } else { - None - } + } else if text_hovered { + let current_row = valid_point.row(); + position_map.display_hunks.iter().find_map(|(hunk, _)| { + if let DisplayDiffHunk::Unfolded { + display_row_range, .. + } = hunk + { + if display_row_range.contains(¤t_row) { + Some(display_row_range.start) } else { None } - }) - } else { - None - } + } else { + None + } + }) + } else { + None }; if hovered_diff_hunk_row != editor.hovered_diff_hunk_row { @@ -1155,14 +1158,14 @@ impl EditorElement { .inline_blame_popover .as_ref() .and_then(|state| state.popover_bounds) - .map_or(false, |bounds| bounds.contains(&event.position)); + .is_some_and(|bounds| bounds.contains(&event.position)); let keyboard_grace = editor .inline_blame_popover .as_ref() - .map_or(false, |state| state.keyboard_grace); + .is_some_and(|state| state.keyboard_grace); if mouse_over_inline_blame || mouse_over_popover { - editor.show_blame_popover(&blame_entry, event.position, false, cx); + editor.show_blame_popover(blame_entry, event.position, false, cx); } else if !keyboard_grace { editor.hide_blame_popover(cx); } @@ -1186,10 +1189,10 @@ impl EditorElement { let is_visible = editor .gutter_breakpoint_indicator .0 - .map_or(false, |indicator| indicator.is_active); + .is_some_and(|indicator| indicator.is_active); let has_existing_breakpoint = - editor.breakpoint_store.as_ref().map_or(false, |store| { + editor.breakpoint_store.as_ref().is_some_and(|store| { let Some(project) = &editor.project else { return false; }; @@ -1387,29 +1390,27 @@ impl EditorElement { ref drop_cursor, ref hide_drop_cursor, } = editor.selection_drag_state + && !hide_drop_cursor + && (drop_cursor + .start + .cmp(&selection.start, &snapshot.buffer_snapshot) + .eq(&Ordering::Less) + || drop_cursor + .end + .cmp(&selection.end, &snapshot.buffer_snapshot) + .eq(&Ordering::Greater)) { - if !hide_drop_cursor - && (drop_cursor - .start - .cmp(&selection.start, &snapshot.buffer_snapshot) - .eq(&Ordering::Less) - || drop_cursor - .end - .cmp(&selection.end, &snapshot.buffer_snapshot) - .eq(&Ordering::Greater)) - { - let drag_cursor_layout = SelectionLayout::new( - drop_cursor.clone(), - false, - CursorShape::Bar, - &snapshot.display_snapshot, - false, - false, - None, - ); - let absent_color = cx.theme().players().absent(); - selections.push((absent_color, vec![drag_cursor_layout])); - } + let drag_cursor_layout = SelectionLayout::new( + drop_cursor.clone(), + false, + CursorShape::Bar, + &snapshot.display_snapshot, + false, + false, + None, + ); + let absent_color = cx.theme().players().absent(); + selections.push((absent_color, vec![drag_cursor_layout])); } } @@ -1420,19 +1421,15 @@ impl EditorElement { CollaboratorId::PeerId(peer_id) => { if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&peer_id) - { - if let Some(participant_index) = collaboration_hub + && let Some(participant_index) = collaboration_hub .user_participant_indices(cx) .get(&collaborator.user_id) - { - if let Some((local_selection_style, _)) = selections.first_mut() - { - *local_selection_style = cx - .theme() - .players() - .color_for_participant(participant_index.0); - } - } + && let Some((local_selection_style, _)) = selections.first_mut() + { + *local_selection_style = cx + .theme() + .players() + .color_for_participant(participant_index.0); } } CollaboratorId::Agent => { @@ -2175,11 +2172,13 @@ impl EditorElement { }; let padding = ProjectSettings::get_global(cx).diagnostics.inline.padding as f32 * em_width; - let min_x = ProjectSettings::get_global(cx) - .diagnostics - .inline - .min_column as f32 - * em_width; + let min_x = self.column_pixels( + ProjectSettings::get_global(cx) + .diagnostics + .inline + .min_column as usize, + window, + ); let mut elements = HashMap::default(); for (row, mut diagnostics) in diagnostics_by_rows { @@ -2220,12 +2219,11 @@ impl EditorElement { cmp::max(padded_line, min_start) }; - let behind_edit_prediction_popover = edit_prediction_popover_origin.as_ref().map_or( - false, - |edit_prediction_popover_origin| { + let behind_edit_prediction_popover = edit_prediction_popover_origin + .as_ref() + .is_some_and(|edit_prediction_popover_origin| { (pos_y..pos_y + line_height).contains(&edit_prediction_popover_origin.y) - }, - ); + }); let opacity = if behind_edit_prediction_popover { 0.5 } else { @@ -2291,9 +2289,7 @@ impl EditorElement { None } }) - .map_or(false, |source| { - matches!(source, CodeActionSource::Indicator(..)) - }); + .is_some_and(|source| matches!(source, CodeActionSource::Indicator(..))); Some(editor.render_inline_code_actions(icon_size, display_point.row(), active, cx)) })?; @@ -2441,14 +2437,13 @@ impl EditorElement { .unwrap_or_default() .padding as f32; - if let Some(edit_prediction) = editor.active_edit_prediction.as_ref() { - match &edit_prediction.completion { - EditPrediction::Edit { - display_mode: EditDisplayMode::TabAccept, - .. - } => padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS, - _ => {} - } + if let Some(edit_prediction) = editor.active_edit_prediction.as_ref() + && let EditPrediction::Edit { + display_mode: EditDisplayMode::TabAccept, + .. + } = &edit_prediction.completion + { + padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS } padding * em_width @@ -2818,7 +2813,7 @@ impl EditorElement { } let row = - MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(&snapshot).row); + MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(snapshot).row); if snapshot.is_line_folded(row) { return None; } @@ -2909,7 +2904,7 @@ impl EditorElement { if multibuffer_row .0 .checked_sub(1) - .map_or(false, |previous_row| { + .is_some_and(|previous_row| { snapshot.is_line_folded(MultiBufferRow(previous_row)) }) { @@ -2982,8 +2977,8 @@ impl EditorElement { .ilog10() + 1; - let elements = buffer_rows - .into_iter() + buffer_rows + .iter() .enumerate() .map(|(ix, row_info)| { let ExpandInfo { @@ -3038,9 +3033,7 @@ impl EditorElement { Some((toggle, origin)) }) - .collect(); - - elements + .collect() } fn calculate_relative_line_numbers( @@ -3140,7 +3133,7 @@ impl EditorElement { let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to); let mut line_number = String::new(); let line_numbers = buffer_rows - .into_iter() + .iter() .enumerate() .flat_map(|(ix, row_info)| { let display_row = DisplayRow(rows.start.0 + ix as u32); @@ -3217,7 +3210,7 @@ impl EditorElement { && self.editor.read(cx).is_singleton(cx); if include_fold_statuses { row_infos - .into_iter() + .iter() .enumerate() .map(|(ix, info)| { if info.expand_info.is_some() { @@ -3312,7 +3305,7 @@ impl EditorElement { let chunks = snapshot.highlighted_chunks(rows.clone(), true, style); LineWithInvisibles::from_chunks( chunks, - &style, + style, MAX_LINE_LEN, rows.len(), &snapshot.mode, @@ -3393,7 +3386,7 @@ impl EditorElement { let line_ix = align_to.row().0.checked_sub(rows.start.0); x_position = if let Some(layout) = line_ix.and_then(|ix| line_layouts.get(ix as usize)) { - x_and_width(&layout) + x_and_width(layout) } else { x_and_width(&layout_line( align_to.row(), @@ -3518,33 +3511,33 @@ impl EditorElement { let mut x_offset = px(0.); let mut is_block = true; - if let BlockId::Custom(custom_block_id) = block_id { - if block.has_height() { - if block.place_near() { - if let Some((x_target, line_width)) = x_position { - let margin = em_width * 2; - if line_width + final_size.width + margin - < editor_width + editor_margins.gutter.full_width() - && !row_block_types.contains_key(&(row - 1)) - && element_height_in_lines == 1 - { - x_offset = line_width + margin; - row = row - 1; - is_block = false; - element_height_in_lines = 0; - row_block_types.insert(row, is_block); - } else { - let max_offset = editor_width + editor_margins.gutter.full_width() - - final_size.width; - let min_offset = (x_target + em_width - final_size.width) - .max(editor_margins.gutter.full_width()); - x_offset = x_target.min(max_offset).max(min_offset); - } - } - }; - if element_height_in_lines != block.height() { - resized_blocks.insert(custom_block_id, element_height_in_lines); + if let BlockId::Custom(custom_block_id) = block_id + && block.has_height() + { + if block.place_near() + && let Some((x_target, line_width)) = x_position + { + let margin = em_width * 2; + if line_width + final_size.width + margin + < editor_width + editor_margins.gutter.full_width() + && !row_block_types.contains_key(&(row - 1)) + && element_height_in_lines == 1 + { + x_offset = line_width + margin; + row = row - 1; + is_block = false; + element_height_in_lines = 0; + row_block_types.insert(row, is_block); + } else { + let max_offset = + editor_width + editor_margins.gutter.full_width() - final_size.width; + let min_offset = (x_target + em_width - final_size.width) + .max(editor_margins.gutter.full_width()); + x_offset = x_target.min(max_offset).max(min_offset); } + }; + if element_height_in_lines != block.height() { + resized_blocks.insert(custom_block_id, element_height_in_lines); } } for i in 0..element_height_in_lines { @@ -3565,9 +3558,8 @@ impl EditorElement { cx: &mut App, ) -> impl IntoElement { let editor = self.editor.read(cx); - let file_status = editor - .buffer - .read(cx) + let multi_buffer = editor.buffer.read(cx); + let file_status = multi_buffer .all_diff_hunks_expanded() .then(|| { editor @@ -3577,6 +3569,17 @@ impl EditorElement { .status_for_buffer_id(for_excerpt.buffer_id, cx) }) .flatten(); + let indicator = multi_buffer + .buffer(for_excerpt.buffer_id) + .and_then(|buffer| { + let buffer = buffer.read(cx); + let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) { + (true, _) => Some(Color::Warning), + (_, true) => Some(Color::Accent), + (false, false) => None, + }; + indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color)) + }); let include_root = editor .project @@ -3685,6 +3688,7 @@ impl EditorElement { }) .take(1), ) + .children(indicator) .child( h_flex() .cursor_pointer() @@ -3889,7 +3893,7 @@ impl EditorElement { for (row, block) in fixed_blocks { let block_id = block.id(); - if focused_block.as_ref().map_or(false, |b| b.id == block_id) { + if focused_block.as_ref().is_some_and(|b| b.id == block_id) { focused_block = None; } @@ -3946,7 +3950,7 @@ impl EditorElement { }; let block_id = block.id(); - if focused_block.as_ref().map_or(false, |b| b.id == block_id) { + if focused_block.as_ref().is_some_and(|b| b.id == block_id) { focused_block = None; } @@ -3987,60 +3991,58 @@ impl EditorElement { } } - if let Some(focused_block) = focused_block { - if let Some(focus_handle) = focused_block.focus_handle.upgrade() { - if focus_handle.is_focused(window) { - if let Some(block) = snapshot.block_for_id(focused_block.id) { - let style = block.style(); - let width = match style { - BlockStyle::Fixed => AvailableSpace::MinContent, - BlockStyle::Flex => AvailableSpace::Definite( - hitbox - .size - .width - .max(fixed_block_max_width) - .max(editor_margins.gutter.width + *scroll_width), - ), - BlockStyle::Sticky => AvailableSpace::Definite(hitbox.size.width), - }; + if let Some(focused_block) = focused_block + && let Some(focus_handle) = focused_block.focus_handle.upgrade() + && focus_handle.is_focused(window) + && let Some(block) = snapshot.block_for_id(focused_block.id) + { + let style = block.style(); + let width = match style { + BlockStyle::Fixed => AvailableSpace::MinContent, + BlockStyle::Flex => AvailableSpace::Definite( + hitbox + .size + .width + .max(fixed_block_max_width) + .max(editor_margins.gutter.width + *scroll_width), + ), + BlockStyle::Sticky => AvailableSpace::Definite(hitbox.size.width), + }; - if let Some((element, element_size, _, x_offset)) = self.render_block( - &block, - width, - focused_block.id, - rows.end, - snapshot, - text_x, - &rows, - line_layouts, - editor_margins, - line_height, - em_width, - text_hitbox, - editor_width, - scroll_width, - &mut resized_blocks, - &mut row_block_types, - selections, - selected_buffer_ids, - is_row_soft_wrapped, - sticky_header_excerpt_id, - window, - cx, - ) { - blocks.push(BlockLayout { - id: block.id(), - x_offset, - row: None, - element, - available_space: size(width, element_size.height.into()), - style, - overlaps_gutter: true, - is_buffer_header: block.is_buffer_header(), - }); - } - } - } + if let Some((element, element_size, _, x_offset)) = self.render_block( + &block, + width, + focused_block.id, + rows.end, + snapshot, + text_x, + &rows, + line_layouts, + editor_margins, + line_height, + em_width, + text_hitbox, + editor_width, + scroll_width, + &mut resized_blocks, + &mut row_block_types, + selections, + selected_buffer_ids, + is_row_soft_wrapped, + sticky_header_excerpt_id, + window, + cx, + ) { + blocks.push(BlockLayout { + id: block.id(), + x_offset, + row: None, + element, + available_space: size(width, element_size.height.into()), + style, + overlaps_gutter: true, + is_buffer_header: block.is_buffer_header(), + }); } } @@ -4203,19 +4205,19 @@ impl EditorElement { edit_prediction_popover_visible = true; } - if editor.context_menu_visible() { - if let Some(crate::ContextMenuOrigin::Cursor) = editor.context_menu_origin() { - let (min_height_in_lines, max_height_in_lines) = editor - .context_menu_options - .as_ref() - .map_or((3, 12), |options| { - (options.min_entries_visible, options.max_entries_visible) - }); + if editor.context_menu_visible() + && let Some(crate::ContextMenuOrigin::Cursor) = editor.context_menu_origin() + { + let (min_height_in_lines, max_height_in_lines) = editor + .context_menu_options + .as_ref() + .map_or((3, 12), |options| { + (options.min_entries_visible, options.max_entries_visible) + }); - min_menu_height += line_height * min_height_in_lines as f32 + POPOVER_Y_PADDING; - max_menu_height += line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING; - context_menu_visible = true; - } + min_menu_height += line_height * min_height_in_lines as f32 + POPOVER_Y_PADDING; + max_menu_height += line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING; + context_menu_visible = true; } context_menu_placement = editor .context_menu_options @@ -4727,7 +4729,7 @@ impl EditorElement { } }; - let source_included = source_display_point.map_or(true, |source_display_point| { + let source_included = source_display_point.is_none_or(|source_display_point| { visible_range .to_inclusive() .contains(&source_display_point.row()) @@ -4907,7 +4909,7 @@ impl EditorElement { let intersects_menu = |bounds: Bounds<Pixels>| -> bool { context_menu_layout .as_ref() - .map_or(false, |menu| bounds.intersects(&menu.bounds)) + .is_some_and(|menu| bounds.intersects(&menu.bounds)) }; let can_place_above = { @@ -5092,7 +5094,7 @@ impl EditorElement { if active_positions .iter() - .any(|p| p.map_or(false, |p| display_row_range.contains(&p.row()))) + .any(|p| p.is_some_and(|p| display_row_range.contains(&p.row()))) { let y = display_row_range.start.as_f32() * line_height + text_hitbox.bounds.top() @@ -5205,7 +5207,7 @@ impl EditorElement { let intersects_menu = |bounds: Bounds<Pixels>| -> bool { context_menu_layout .as_ref() - .map_or(false, |menu| bounds.intersects(&menu.bounds)) + .is_some_and(|menu| bounds.intersects(&menu.bounds)) }; let final_origin = if popover_bounds_above.is_contained_within(hitbox) @@ -5290,7 +5292,7 @@ impl EditorElement { let mut end_row = start_row.0; while active_rows .peek() - .map_or(false, |(active_row, has_selection)| { + .is_some_and(|(active_row, has_selection)| { active_row.0 == end_row + 1 && has_selection.selection == contains_non_empty_selection.selection }) @@ -5549,9 +5551,9 @@ impl EditorElement { // In singleton buffers, we select corresponding lines on the line number click, so use | -like cursor. // In multi buffers, we open file at the line number clicked, so use a pointing hand cursor. if is_singleton { - window.set_cursor_style(CursorStyle::IBeam, &hitbox); + window.set_cursor_style(CursorStyle::IBeam, hitbox); } else { - window.set_cursor_style(CursorStyle::PointingHand, &hitbox); + window.set_cursor_style(CursorStyle::PointingHand, hitbox); } } } @@ -5570,7 +5572,7 @@ impl EditorElement { &layout.position_map.snapshot, line_height, layout.gutter_hitbox.bounds, - &hunk, + hunk, ); Some(( hunk_bounds, @@ -5761,16 +5763,15 @@ impl EditorElement { cx: &mut App, ) { for (_, hunk_hitbox) in &layout.display_hunks { - if let Some(hunk_hitbox) = hunk_hitbox { - if !self + if let Some(hunk_hitbox) = hunk_hitbox + && !self .editor .read(cx) .buffer() .read(cx) .all_diff_hunks_expanded() - { - window.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox); - } + { + window.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox); } } @@ -6092,10 +6093,10 @@ impl EditorElement { if axis == ScrollbarAxis::Vertical { let fast_markers = - self.collect_fast_scrollbar_markers(layout, &scrollbar_layout, cx); + self.collect_fast_scrollbar_markers(layout, scrollbar_layout, cx); // Refresh slow scrollbar markers in the background. Below, we // paint whatever markers have already been computed. - self.refresh_slow_scrollbar_markers(layout, &scrollbar_layout, window, cx); + self.refresh_slow_scrollbar_markers(layout, scrollbar_layout, window, cx); let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone(); for marker in markers.iter().chain(&fast_markers) { @@ -6129,7 +6130,7 @@ impl EditorElement { if any_scrollbar_dragged { window.set_window_cursor_style(CursorStyle::Arrow); } else { - window.set_cursor_style(CursorStyle::Arrow, &hitbox); + window.set_cursor_style(CursorStyle::Arrow, hitbox); } } }) @@ -6679,25 +6680,23 @@ impl EditorElement { editor.set_scroll_position(position, window, cx); } cx.stop_propagation(); - } else { - if minimap_hitbox.is_hovered(window) { - editor.scroll_manager.set_is_hovering_minimap_thumb( - !event.dragging() - && layout - .thumb_layout - .thumb_bounds - .is_some_and(|bounds| bounds.contains(&event.position)), - cx, - ); + } else if minimap_hitbox.is_hovered(window) { + editor.scroll_manager.set_is_hovering_minimap_thumb( + !event.dragging() + && layout + .thumb_layout + .thumb_bounds + .is_some_and(|bounds| bounds.contains(&event.position)), + cx, + ); - // Stop hover events from propagating to the - // underlying editor if the minimap hitbox is hovered - if !event.dragging() { - cx.stop_propagation(); - } - } else { - editor.scroll_manager.hide_minimap_thumb(cx); + // Stop hover events from propagating to the + // underlying editor if the minimap hitbox is hovered + if !event.dragging() { + cx.stop_propagation(); } + } else { + editor.scroll_manager.hide_minimap_thumb(cx); } mouse_position = event.position; }); @@ -7076,9 +7075,7 @@ impl EditorElement { let unstaged_hollow = ProjectSettings::get_global(cx) .git .hunk_style - .map_or(false, |style| { - matches!(style, GitHunkStyleSetting::UnstagedHollow) - }); + .is_some_and(|style| matches!(style, GitHunkStyleSetting::UnstagedHollow)); unstaged == unstaged_hollow } @@ -8175,7 +8172,7 @@ impl Element for EditorElement { let is_row_soft_wrapped = |row: usize| { row_infos .get(row) - .map_or(true, |info| info.buffer_row.is_none()) + .is_none_or(|info| info.buffer_row.is_none()) }; let start_anchor = if start_row == Default::default() { @@ -9710,14 +9707,12 @@ impl PointForPosition { false } else if start_row == end_row { candidate_col >= start_col && candidate_col < end_col + } else if candidate_row == start_row { + candidate_col >= start_col + } else if candidate_row == end_row { + candidate_col < end_col } else { - if candidate_row == start_row { - candidate_col >= start_col - } else if candidate_row == end_row { - candidate_col < end_col - } else { - true - } + true } } } @@ -9782,7 +9777,7 @@ pub fn layout_line( let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style); LineWithInvisibles::from_chunks( chunks, - &style, + style, MAX_LINE_LEN, 1, &snapshot.mode, @@ -10152,10 +10147,10 @@ fn compute_auto_height_layout( let overscroll = size(em_width, px(0.)); let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width; - if !matches!(editor.soft_wrap_mode(cx), SoftWrap::None) { - if editor.set_wrap_width(Some(editor_width), cx) { - snapshot = editor.snapshot(window, cx); - } + if !matches!(editor.soft_wrap_mode(cx), SoftWrap::None) + && editor.set_wrap_width(Some(editor_width), cx) + { + snapshot = editor.snapshot(window, cx); } let scroll_height = (snapshot.max_point().row().next_row().0 as f32) * line_height; @@ -10187,6 +10182,71 @@ mod tests { use std::num::NonZeroU32; use util::test::sample_text; + #[gpui::test] + async fn test_soft_wrap_editor_width_auto_height_editor(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let window = cx.add_window(|window, cx| { + let buffer = MultiBuffer::build_simple(&"a ".to_string().repeat(100), cx); + let mut editor = Editor::new( + EditorMode::AutoHeight { + min_lines: 1, + max_lines: None, + }, + buffer, + None, + window, + cx, + ); + editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); + editor + }); + let cx = &mut VisualTestContext::from_window(*window, cx); + let editor = window.root(cx).unwrap(); + let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone()); + + for x in 1..=100 { + let (_, state) = cx.draw( + Default::default(), + size(px(200. + 0.13 * x as f32), px(500.)), + |_, _| EditorElement::new(&editor, style.clone()), + ); + + assert!( + state.position_map.scroll_max.x == 0., + "Soft wrapped editor should have no horizontal scrolling!" + ); + } + } + + #[gpui::test] + async fn test_soft_wrap_editor_width_full_editor(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let window = cx.add_window(|window, cx| { + let buffer = MultiBuffer::build_simple(&"a ".to_string().repeat(100), cx); + let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx); + editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); + editor + }); + let cx = &mut VisualTestContext::from_window(*window, cx); + let editor = window.root(cx).unwrap(); + let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone()); + + for x in 1..=100 { + let (_, state) = cx.draw( + Default::default(), + size(px(200. + 0.13 * x as f32), px(500.)), + |_, _| EditorElement::new(&editor, style.clone()), + ); + + assert!( + state.position_map.scroll_max.x == 0., + "Soft wrapped editor should have no horizontal scrolling!" + ); + } + } + #[gpui::test] fn test_shape_line_numbers(cx: &mut TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index fc350a5a15..b11617ccec 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -213,8 +213,8 @@ impl GitBlame { let project_subscription = cx.subscribe(&project, { let buffer = buffer.clone(); - move |this, _, event, cx| match event { - project::Event::WorktreeUpdatedEntries(_, updated) => { + move |this, _, event, cx| { + if let project::Event::WorktreeUpdatedEntries(_, updated) = event { let project_entry_id = buffer.read(cx).entry_id(cx); if updated .iter() @@ -224,7 +224,6 @@ impl GitBlame { this.generate(cx); } } - _ => {} } }); @@ -292,7 +291,7 @@ impl GitBlame { let buffer_id = self.buffer_snapshot.remote_id(); let mut cursor = self.entries.cursor::<u32>(&()); - rows.into_iter().map(move |info| { + rows.iter().map(move |info| { let row = info .buffer_row .filter(|_| info.buffer_id == Some(buffer_id))?; @@ -312,10 +311,10 @@ impl GitBlame { .as_ref() .and_then(|entry| entry.author.as_ref()) .map(|author| author.len()); - if let Some(author_len) = author_len { - if author_len > max_author_length { - max_author_length = author_len; - } + if let Some(author_len) = author_len + && author_len > max_author_length + { + max_author_length = author_len; } } @@ -415,21 +414,20 @@ impl GitBlame { let old_end = cursor.end(); if row_edits .peek() - .map_or(true, |next_edit| next_edit.old.start >= old_end) + .is_none_or(|next_edit| next_edit.old.start >= old_end) + && let Some(entry) = cursor.item() { - if let Some(entry) = cursor.item() { - if old_end > edit.old.end { - new_entries.push( - GitBlameEntry { - rows: cursor.end() - edit.old.end, - blame: entry.blame.clone(), - }, - &(), - ); - } - - cursor.next(); + if old_end > edit.old.end { + new_entries.push( + GitBlameEntry { + rows: cursor.end() - edit.old.end, + blame: entry.blame.clone(), + }, + &(), + ); } + + cursor.next(); } } new_entries.append(cursor.suffix(), &()); diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 02f93e6829..04e66a234c 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -271,7 +271,7 @@ impl Editor { Task::ready(Ok(Navigated::No)) }; self.select(SelectPhase::End, window, cx); - return navigate_task; + navigate_task } } @@ -418,24 +418,22 @@ pub fn update_inlay_link_and_hover_points( } if let Some((language_server_id, location)) = hovered_hint_part.location + && secondary_held + && !editor.has_pending_nonempty_selection() { - if secondary_held - && !editor.has_pending_nonempty_selection() - { - go_to_definition_updated = true; - show_link_definition( - shift_held, - editor, - TriggerPoint::InlayHint( - highlight, - location, - language_server_id, - ), - snapshot, - window, - cx, - ); - } + go_to_definition_updated = true; + show_link_definition( + shift_held, + editor, + TriggerPoint::InlayHint( + highlight, + location, + language_server_id, + ), + snapshot, + window, + cx, + ); } } } @@ -657,11 +655,11 @@ pub fn show_link_definition( pub(crate) fn find_url( buffer: &Entity<language::Buffer>, position: text::Anchor, - mut cx: AsyncWindowContext, + cx: AsyncWindowContext, ) -> Option<(Range<text::Anchor>, String)> { const LIMIT: usize = 2048; - let Ok(snapshot) = buffer.read_with(&mut cx, |buffer, _| buffer.snapshot()) else { + let Ok(snapshot) = buffer.read_with(&cx, |buffer, _| buffer.snapshot()) else { return None; }; @@ -719,11 +717,11 @@ pub(crate) fn find_url( pub(crate) fn find_url_from_range( buffer: &Entity<language::Buffer>, range: Range<text::Anchor>, - mut cx: AsyncWindowContext, + cx: AsyncWindowContext, ) -> Option<String> { const LIMIT: usize = 2048; - let Ok(snapshot) = buffer.read_with(&mut cx, |buffer, _| buffer.snapshot()) else { + let Ok(snapshot) = buffer.read_with(&cx, |buffer, _| buffer.snapshot()) else { return None; }; @@ -766,10 +764,11 @@ pub(crate) fn find_url_from_range( let mut finder = LinkFinder::new(); finder.kinds(&[LinkKind::Url]); - if let Some(link) = finder.links(&text).next() { - if link.start() == 0 && link.end() == text.len() { - return Some(link.as_str().to_string()); - } + if let Some(link) = finder.links(&text).next() + && link.start() == 0 + && link.end() == text.len() + { + return Some(link.as_str().to_string()); } None @@ -794,7 +793,7 @@ pub(crate) async fn find_file( ) -> Option<ResolvedPath> { project .update(cx, |project, cx| { - project.resolve_path_in_buffer(&candidate_file_path, buffer, cx) + project.resolve_path_in_buffer(candidate_file_path, buffer, cx) }) .ok()? .await @@ -872,7 +871,7 @@ fn surrounding_filename( .peekable(); while let Some(ch) = forwards.next() { // Skip escaped whitespace - if ch == '\\' && forwards.peek().map_or(false, |ch| ch.is_whitespace()) { + if ch == '\\' && forwards.peek().is_some_and(|ch| ch.is_whitespace()) { token_end += ch.len_utf8(); let whitespace = forwards.next().unwrap(); token_end += whitespace.len_utf8(); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index a443de6c34..6efc88737a 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -142,11 +142,11 @@ pub fn hover_at_inlay( .info_popovers .iter() .any(|InfoPopover { symbol_range, .. }| { - if let RangeInEditor::Inlay(range) = symbol_range { - if range == &inlay_hover.range { - // Hover triggered from same location as last time. Don't show again. - return true; - } + if let RangeInEditor::Inlay(range) = symbol_range + && range == &inlay_hover.range + { + // Hover triggered from same location as last time. Don't show again. + return true; } false }) @@ -167,7 +167,8 @@ pub fn hover_at_inlay( let language_registry = project.read_with(cx, |p, _| p.languages().clone())?; let blocks = vec![inlay_hover.tooltip]; - let parsed_content = parse_blocks(&blocks, &language_registry, None, cx).await; + let parsed_content = + parse_blocks(&blocks, Some(&language_registry), None, cx).await; let scroll_handle = ScrollHandle::new(); @@ -250,7 +251,9 @@ fn show_hover( let (excerpt_id, _, _) = editor.buffer().read(cx).excerpt_containing(anchor, cx)?; - let language_registry = editor.project()?.read(cx).languages().clone(); + let language_registry = editor + .project() + .map(|project| project.read(cx).languages().clone()); let provider = editor.semantics_provider.clone()?; if !ignore_timeout { @@ -266,13 +269,12 @@ fn show_hover( } // Don't request again if the location is the same as the previous request - if let Some(triggered_from) = &editor.hover_state.triggered_from { - if triggered_from + if let Some(triggered_from) = &editor.hover_state.triggered_from + && triggered_from .cmp(&anchor, &snapshot.buffer_snapshot) .is_eq() - { - return None; - } + { + return None; } let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay; @@ -441,7 +443,8 @@ fn show_hover( text: format!("Unicode character U+{:02X}", invisible as u32), kind: HoverBlockKind::PlainText, }]; - let parsed_content = parse_blocks(&blocks, &language_registry, None, cx).await; + let parsed_content = + parse_blocks(&blocks, language_registry.as_ref(), None, cx).await; let scroll_handle = ScrollHandle::new(); let subscription = this .update(cx, |_, cx| { @@ -490,7 +493,8 @@ fn show_hover( let blocks = hover_result.contents; let language = hover_result.language; - let parsed_content = parse_blocks(&blocks, &language_registry, language, cx).await; + let parsed_content = + parse_blocks(&blocks, language_registry.as_ref(), language, cx).await; let scroll_handle = ScrollHandle::new(); hover_highlights.push(range.clone()); let subscription = this @@ -579,7 +583,7 @@ fn same_diagnostic_hover(editor: &Editor, snapshot: &EditorSnapshot, anchor: Anc async fn parse_blocks( blocks: &[HoverBlock], - language_registry: &Arc<LanguageRegistry>, + language_registry: Option<&Arc<LanguageRegistry>>, language: Option<Arc<Language>>, cx: &mut AsyncWindowContext, ) -> Option<Entity<Markdown>> { @@ -595,18 +599,15 @@ async fn parse_blocks( }) .join("\n\n"); - let rendered_block = cx - .new_window_entity(|_window, cx| { - Markdown::new( - combined_text.into(), - Some(language_registry.clone()), - language.map(|language| language.name()), - cx, - ) - }) - .ok(); - - rendered_block + cx.new_window_entity(|_window, cx| { + Markdown::new( + combined_text.into(), + language_registry.cloned(), + language.map(|language| language.name()), + cx, + ) + }) + .ok() } pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { @@ -708,59 +709,54 @@ pub fn diagnostics_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { } pub fn open_markdown_url(link: SharedString, window: &mut Window, cx: &mut App) { - if let Ok(uri) = Url::parse(&link) { - if uri.scheme() == "file" { - if let Some(workspace) = window.root::<Workspace>().flatten() { - workspace.update(cx, |workspace, cx| { - let task = workspace.open_abs_path( - PathBuf::from(uri.path()), - OpenOptions { - visible: Some(OpenVisible::None), - ..Default::default() - }, - window, - cx, - ); + if let Ok(uri) = Url::parse(&link) + && uri.scheme() == "file" + && let Some(workspace) = window.root::<Workspace>().flatten() + { + workspace.update(cx, |workspace, cx| { + let task = workspace.open_abs_path( + PathBuf::from(uri.path()), + OpenOptions { + visible: Some(OpenVisible::None), + ..Default::default() + }, + window, + cx, + ); - cx.spawn_in(window, async move |_, cx| { - let item = task.await?; - // Ruby LSP uses URLs with #L1,1-4,4 - // we'll just take the first number and assume it's a line number - let Some(fragment) = uri.fragment() else { - return anyhow::Ok(()); - }; - let mut accum = 0u32; - for c in fragment.chars() { - if c >= '0' && c <= '9' && accum < u32::MAX / 2 { - accum *= 10; - accum += c as u32 - '0' as u32; - } else if accum > 0 { - break; - } - } - if accum == 0 { - return Ok(()); - } - let Some(editor) = cx.update(|_, cx| item.act_as::<Editor>(cx))? else { - return Ok(()); - }; - editor.update_in(cx, |editor, window, cx| { - editor.change_selections( - Default::default(), - window, - cx, - |selections| { - selections.select_ranges([text::Point::new(accum - 1, 0) - ..text::Point::new(accum - 1, 0)]); - }, - ); - }) - }) - .detach_and_log_err(cx); - }); - return; - } - } + cx.spawn_in(window, async move |_, cx| { + let item = task.await?; + // Ruby LSP uses URLs with #L1,1-4,4 + // we'll just take the first number and assume it's a line number + let Some(fragment) = uri.fragment() else { + return anyhow::Ok(()); + }; + let mut accum = 0u32; + for c in fragment.chars() { + if c >= '0' && c <= '9' && accum < u32::MAX / 2 { + accum *= 10; + accum += c as u32 - '0' as u32; + } else if accum > 0 { + break; + } + } + if accum == 0 { + return Ok(()); + } + let Some(editor) = cx.update(|_, cx| item.act_as::<Editor>(cx))? else { + return Ok(()); + }; + editor.update_in(cx, |editor, window, cx| { + editor.change_selections(Default::default(), window, cx, |selections| { + selections.select_ranges([ + text::Point::new(accum - 1, 0)..text::Point::new(accum - 1, 0) + ]); + }); + }) + }) + .detach_and_log_err(cx); + }); + return; } cx.open_url(&link); } @@ -830,21 +826,20 @@ impl HoverState { pub fn focused(&self, window: &mut Window, cx: &mut Context<Editor>) -> bool { let mut hover_popover_is_focused = false; for info_popover in &self.info_popovers { - if let Some(markdown_view) = &info_popover.parsed_content { - if markdown_view.focus_handle(cx).is_focused(window) { - hover_popover_is_focused = true; - } - } - } - if let Some(diagnostic_popover) = &self.diagnostic_popover { - if diagnostic_popover - .markdown - .focus_handle(cx) - .is_focused(window) + if let Some(markdown_view) = &info_popover.parsed_content + && markdown_view.focus_handle(cx).is_focused(window) { hover_popover_is_focused = true; } } + if let Some(diagnostic_popover) = &self.diagnostic_popover + && diagnostic_popover + .markdown + .focus_handle(cx) + .is_focused(window) + { + hover_popover_is_focused = true; + } hover_popover_is_focused } } diff --git a/crates/editor/src/indent_guides.rs b/crates/editor/src/indent_guides.rs index f6d51c929a..a1de2b604b 100644 --- a/crates/editor/src/indent_guides.rs +++ b/crates/editor/src/indent_guides.rs @@ -168,11 +168,11 @@ pub fn indent_guides_in_range( while let Some(fold) = folds.next() { let start = fold.range.start.to_point(&snapshot.buffer_snapshot); let end = fold.range.end.to_point(&snapshot.buffer_snapshot); - if let Some(last_range) = fold_ranges.last_mut() { - if last_range.end >= start { - last_range.end = last_range.end.max(end); - continue; - } + if let Some(last_range) = fold_ranges.last_mut() + && last_range.end >= start + { + last_range.end = last_range.end.max(end); + continue; } fold_ranges.push(start..end); } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 60ad0e5bf6..cea0e32d7f 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -498,16 +498,14 @@ impl InlayHintCache { cmp::Ordering::Less | cmp::Ordering::Equal => { if !old_kinds.contains(&cached_hint.kind) && new_kinds.contains(&cached_hint.kind) - { - if let Some(anchor) = multi_buffer_snapshot + && let Some(anchor) = multi_buffer_snapshot .anchor_in_excerpt(*excerpt_id, cached_hint.position) - { - to_insert.push(Inlay::hint( - cached_hint_id.id(), - anchor, - cached_hint, - )); - } + { + to_insert.push(Inlay::hint( + cached_hint_id.id(), + anchor, + cached_hint, + )); } excerpt_cache.next(); } @@ -522,16 +520,16 @@ impl InlayHintCache { for cached_hint_id in excerpt_cache { let maybe_missed_cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id]; let cached_hint_kind = maybe_missed_cached_hint.kind; - if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { - if let Some(anchor) = multi_buffer_snapshot + if !old_kinds.contains(&cached_hint_kind) + && new_kinds.contains(&cached_hint_kind) + && let Some(anchor) = multi_buffer_snapshot .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position) - { - to_insert.push(Inlay::hint( - cached_hint_id.id(), - anchor, - maybe_missed_cached_hint, - )); - } + { + to_insert.push(Inlay::hint( + cached_hint_id.id(), + anchor, + maybe_missed_cached_hint, + )); } } } @@ -620,44 +618,44 @@ impl InlayHintCache { ) { if let Some(excerpt_hints) = self.hints.get(&excerpt_id) { let mut guard = excerpt_hints.write(); - if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) { - if let ResolveState::CanResolve(server_id, _) = &cached_hint.resolve_state { - let hint_to_resolve = cached_hint.clone(); - let server_id = *server_id; - cached_hint.resolve_state = ResolveState::Resolving; - drop(guard); - cx.spawn_in(window, async move |editor, cx| { - let resolved_hint_task = editor.update(cx, |editor, cx| { - let buffer = editor.buffer().read(cx).buffer(buffer_id)?; - editor.semantics_provider.as_ref()?.resolve_inlay_hint( - hint_to_resolve, - buffer, - server_id, - cx, - ) - })?; - if let Some(resolved_hint_task) = resolved_hint_task { - let mut resolved_hint = - resolved_hint_task.await.context("hint resolve task")?; - editor.read_with(cx, |editor, _| { - if let Some(excerpt_hints) = - editor.inlay_hint_cache.hints.get(&excerpt_id) + if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) + && let ResolveState::CanResolve(server_id, _) = &cached_hint.resolve_state + { + let hint_to_resolve = cached_hint.clone(); + let server_id = *server_id; + cached_hint.resolve_state = ResolveState::Resolving; + drop(guard); + cx.spawn_in(window, async move |editor, cx| { + let resolved_hint_task = editor.update(cx, |editor, cx| { + let buffer = editor.buffer().read(cx).buffer(buffer_id)?; + editor.semantics_provider.as_ref()?.resolve_inlay_hint( + hint_to_resolve, + buffer, + server_id, + cx, + ) + })?; + if let Some(resolved_hint_task) = resolved_hint_task { + let mut resolved_hint = + resolved_hint_task.await.context("hint resolve task")?; + editor.read_with(cx, |editor, _| { + if let Some(excerpt_hints) = + editor.inlay_hint_cache.hints.get(&excerpt_id) + { + let mut guard = excerpt_hints.write(); + if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) + && cached_hint.resolve_state == ResolveState::Resolving { - let mut guard = excerpt_hints.write(); - if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) { - if cached_hint.resolve_state == ResolveState::Resolving { - resolved_hint.resolve_state = ResolveState::Resolved; - *cached_hint = resolved_hint; - } - } + resolved_hint.resolve_state = ResolveState::Resolved; + *cached_hint = resolved_hint; } - })?; - } + } + })?; + } - anyhow::Ok(()) - }) - .detach_and_log_err(cx); - } + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } } } @@ -990,8 +988,8 @@ fn fetch_and_update_hints( let buffer = editor.buffer().read(cx).buffer(query.buffer_id)?; - if !editor.registered_buffers.contains_key(&query.buffer_id) { - if let Some(project) = editor.project.as_ref() { + if !editor.registered_buffers.contains_key(&query.buffer_id) + && let Some(project) = editor.project.as_ref() { project.update(cx, |project, cx| { editor.registered_buffers.insert( query.buffer_id, @@ -999,7 +997,6 @@ fn fetch_and_update_hints( ); }) } - } editor .semantics_provider @@ -1240,14 +1237,12 @@ fn apply_hint_update( .inlay_hint_cache .allowed_hint_kinds .contains(&new_hint.kind) - { - if let Some(new_hint_position) = + && let Some(new_hint_position) = multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position) - { - splice - .to_insert - .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint)); - } + { + splice + .to_insert + .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint)); } let new_id = InlayId::Hint(new_inlay_id); cached_excerpt_hints.hints_by_id.insert(new_id, new_hint); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 34533002ff..8957e0e99c 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -201,7 +201,7 @@ impl FollowableItem for Editor { if buffer .as_singleton() .and_then(|buffer| buffer.read(cx).file()) - .map_or(false, |file| file.is_private()) + .is_some_and(|file| file.is_private()) { return None; } @@ -524,8 +524,8 @@ fn serialize_selection( ) -> proto::Selection { proto::Selection { id: selection.id as u64, - start: Some(serialize_anchor(&selection.start, &buffer)), - end: Some(serialize_anchor(&selection.end, &buffer)), + start: Some(serialize_anchor(&selection.start, buffer)), + end: Some(serialize_anchor(&selection.end, buffer)), reversed: selection.reversed, } } @@ -715,7 +715,7 @@ impl Item for Editor { .read(cx) .as_singleton() .and_then(|buffer| buffer.read(cx).file()) - .map_or(false, |file| file.disk_state() == DiskState::Deleted); + .is_some_and(|file| file.disk_state() == DiskState::Deleted); h_flex() .gap_2() @@ -930,10 +930,10 @@ impl Item for Editor { })?; buffer .update(cx, |buffer, cx| { - if let Some(transaction) = transaction { - if !buffer.is_singleton() { - buffer.push_transaction(&transaction.0, cx); - } + if let Some(transaction) = transaction + && !buffer.is_singleton() + { + buffer.push_transaction(&transaction.0, cx); } }) .ok(); @@ -1009,16 +1009,12 @@ impl Item for Editor { ) { self.workspace = Some((workspace.weak_handle(), workspace.database_id())); if let Some(workspace) = &workspace.weak_handle().upgrade() { - cx.subscribe( - &workspace, - |editor, _, event: &workspace::Event, _cx| match event { - workspace::Event::ModalOpened => { - editor.mouse_context_menu.take(); - editor.inline_blame_popover.take(); - } - _ => {} - }, - ) + cx.subscribe(workspace, |editor, _, event: &workspace::Event, _cx| { + if let workspace::Event::ModalOpened = event { + editor.mouse_context_menu.take(); + editor.inline_blame_popover.take(); + } + }) .detach(); } } @@ -1296,7 +1292,7 @@ impl SerializableItem for Editor { project .read(cx) .worktree_for_id(worktree_id, cx) - .and_then(|worktree| worktree.read(cx).absolutize(&file.path()).ok()) + .and_then(|worktree| worktree.read(cx).absolutize(file.path()).ok()) .or_else(|| { let full_path = file.full_path(cx); let project_path = project.read(cx).find_project_path(&full_path, cx)?; @@ -1374,36 +1370,33 @@ impl ProjectItem for Editor { let mut editor = Self::for_buffer(buffer.clone(), Some(project), window, cx); if let Some((excerpt_id, buffer_id, snapshot)) = editor.buffer().read(cx).snapshot(cx).as_singleton() + && WorkspaceSettings::get(None, cx).restore_on_file_reopen + && let Some(restoration_data) = Self::project_item_kind() + .and_then(|kind| pane.as_ref()?.project_item_restoration_data.get(&kind)) + .and_then(|data| data.downcast_ref::<EditorRestorationData>()) + .and_then(|data| { + let file = project::File::from_dyn(buffer.read(cx).file())?; + data.entries.get(&file.abs_path(cx)) + }) { - if WorkspaceSettings::get(None, cx).restore_on_file_reopen { - if let Some(restoration_data) = Self::project_item_kind() - .and_then(|kind| pane.as_ref()?.project_item_restoration_data.get(&kind)) - .and_then(|data| data.downcast_ref::<EditorRestorationData>()) - .and_then(|data| { - let file = project::File::from_dyn(buffer.read(cx).file())?; - data.entries.get(&file.abs_path(cx)) - }) - { - editor.fold_ranges( - clip_ranges(&restoration_data.folds, &snapshot), - false, - window, - cx, - ); - if !restoration_data.selections.is_empty() { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges(clip_ranges(&restoration_data.selections, &snapshot)); - }); - } - let (top_row, offset) = restoration_data.scroll_position; - let anchor = Anchor::in_buffer( - *excerpt_id, - buffer_id, - snapshot.anchor_before(Point::new(top_row, 0)), - ); - editor.set_scroll_anchor(ScrollAnchor { anchor, offset }, window, cx); - } + editor.fold_ranges( + clip_ranges(&restoration_data.folds, snapshot), + false, + window, + cx, + ); + if !restoration_data.selections.is_empty() { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(clip_ranges(&restoration_data.selections, snapshot)); + }); } + let (top_row, offset) = restoration_data.scroll_position; + let anchor = Anchor::in_buffer( + *excerpt_id, + buffer_id, + snapshot.anchor_before(Point::new(top_row, 0)), + ); + editor.set_scroll_anchor(ScrollAnchor { anchor, offset }, window, cx); } editor diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index 95a7925839..a3fc41228f 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/crates/editor/src/jsx_tag_auto_close.rs @@ -37,7 +37,7 @@ pub(crate) fn should_auto_close( let text = buffer .text_for_range(edited_range.clone()) .collect::<String>(); - let edited_range = edited_range.to_offset(&buffer); + let edited_range = edited_range.to_offset(buffer); if !text.ends_with(">") { continue; } @@ -51,12 +51,11 @@ pub(crate) fn should_auto_close( continue; }; let mut jsx_open_tag_node = node; - if node.grammar_name() != config.open_tag_node_name { - if let Some(parent) = node.parent() { - if parent.grammar_name() == config.open_tag_node_name { - jsx_open_tag_node = parent; - } - } + if node.grammar_name() != config.open_tag_node_name + && let Some(parent) = node.parent() + && parent.grammar_name() == config.open_tag_node_name + { + jsx_open_tag_node = parent; } if jsx_open_tag_node.grammar_name() != config.open_tag_node_name { continue; @@ -87,9 +86,9 @@ pub(crate) fn should_auto_close( }); } if to_auto_edit.is_empty() { - return None; + None } else { - return Some(to_auto_edit); + Some(to_auto_edit) } } @@ -187,7 +186,7 @@ pub(crate) fn generate_auto_close_edits( let range = node_name.byte_range(); return buffer.text_for_range(range).equals_str(name); } - return is_empty; + is_empty }; let tree_root_node = { @@ -228,7 +227,7 @@ pub(crate) fn generate_auto_close_edits( let has_open_tag_with_same_tag_name = ancestor .named_child(0) .filter(|n| n.kind() == config.open_tag_node_name) - .map_or(false, |element_open_tag_node| { + .is_some_and(|element_open_tag_node| { tag_node_name_equals(&element_open_tag_node, &tag_name) }); if has_open_tag_with_same_tag_name { @@ -264,8 +263,7 @@ pub(crate) fn generate_auto_close_edits( } let is_after_open_tag = |node: &Node| { - return node.start_byte() < open_tag.start_byte() - && node.end_byte() < open_tag.start_byte(); + node.start_byte() < open_tag.start_byte() && node.end_byte() < open_tag.start_byte() }; // perf: use cursor for more efficient traversal @@ -284,10 +282,8 @@ pub(crate) fn generate_auto_close_edits( unclosed_open_tag_count -= 1; } } else if has_erroneous_close_tag && kind == erroneous_close_tag_node_name { - if tag_node_name_equals(&node, &tag_name) { - if !is_after_open_tag(&node) { - unclosed_open_tag_count -= 1; - } + if tag_node_name_equals(&node, &tag_name) && !is_after_open_tag(&node) { + unclosed_open_tag_count -= 1; } } else if kind == config.jsx_element_node_name { // perf: filter only open,close,element,erroneous nodes @@ -304,7 +300,7 @@ pub(crate) fn generate_auto_close_edits( let edit_range = edit_anchor..edit_anchor; edits.push((edit_range, format!("</{}>", tag_name))); } - return Ok(edits); + Ok(edits) } pub(crate) fn refresh_enabled_in_any_buffer( @@ -370,7 +366,7 @@ pub(crate) fn construct_initial_buffer_versions_map< initial_buffer_versions.insert(buffer_id, buffer_version); } } - return initial_buffer_versions; + initial_buffer_versions } pub(crate) fn handle_from( @@ -458,12 +454,9 @@ pub(crate) fn handle_from( let ensure_no_edits_since_start = || -> Option<()> { let has_edits_since_start = this .read_with(cx, |this, cx| { - this.buffer - .read(cx) - .buffer(buffer_id) - .map_or(true, |buffer| { - buffer.read(cx).has_edits_since(&buffer_version_initial) - }) + this.buffer.read(cx).buffer(buffer_id).is_none_or(|buffer| { + buffer.read(cx).has_edits_since(&buffer_version_initial) + }) }) .ok()?; @@ -815,10 +808,7 @@ mod jsx_tag_autoclose_tests { ); buf }); - let buffer_c = cx.new(|cx| { - let buf = language::Buffer::local("<span", cx); - buf - }); + let buffer_c = cx.new(|cx| language::Buffer::local("<span", cx)); let buffer = cx.new(|cx| { let mut buf = MultiBuffer::new(language::Capability::ReadWrite); buf.push_excerpts( diff --git a/crates/editor/src/lsp_colors.rs b/crates/editor/src/lsp_colors.rs index 08cf9078f2..29eb9f249a 100644 --- a/crates/editor/src/lsp_colors.rs +++ b/crates/editor/src/lsp_colors.rs @@ -207,7 +207,7 @@ impl Editor { .entry(buffer_snapshot.remote_id()) .or_insert_with(Vec::new); let excerpt_point_range = - excerpt_range.context.to_point_utf16(&buffer_snapshot); + excerpt_range.context.to_point_utf16(buffer_snapshot); excerpt_data.push(( excerpt_id, buffer_snapshot.clone(), diff --git a/crates/editor/src/lsp_ext.rs b/crates/editor/src/lsp_ext.rs index 6161afbbc0..18ad2d71c8 100644 --- a/crates/editor/src/lsp_ext.rs +++ b/crates/editor/src/lsp_ext.rs @@ -76,7 +76,7 @@ async fn lsp_task_context( let project_env = project .update(cx, |project, cx| { - project.buffer_environment(&buffer, &worktree_store, cx) + project.buffer_environment(buffer, &worktree_store, cx) }) .ok()? .await; @@ -147,16 +147,15 @@ pub fn lsp_tasks( }, cx, ) - }) { - if let Some(new_runnables) = runnables_task.await.log_err() { - new_lsp_tasks.extend(new_runnables.runnables.into_iter().filter_map( - |(location, runnable)| { - let resolved_task = - runnable.resolve_task(&id_base, &lsp_buffer_context)?; - Some((location, resolved_task)) - }, - )); - } + }) && let Some(new_runnables) = runnables_task.await.log_err() + { + new_lsp_tasks.extend(new_runnables.runnables.into_iter().filter_map( + |(location, runnable)| { + let resolved_task = + runnable.resolve_task(&id_base, &lsp_buffer_context)?; + Some((location, resolved_task)) + }, + )); } lsp_tasks .entry(source_kind) diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 9d5145dec1..5cf22de537 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -61,13 +61,13 @@ impl MouseContextMenu { source, offset: position - (source_position + content_origin), }; - return Some(MouseContextMenu::new( + Some(MouseContextMenu::new( editor, menu_position, context_menu, window, cx, - )); + )) } pub(crate) fn new( @@ -102,11 +102,11 @@ impl MouseContextMenu { let display_snapshot = &editor .display_map .update(cx, |display_map, cx| display_map.snapshot(cx)); - let selection_init_range = selection_init.display_range(&display_snapshot); + let selection_init_range = selection_init.display_range(display_snapshot); let selection_now_range = editor .selections .newest_anchor() - .display_range(&display_snapshot); + .display_range(display_snapshot); if selection_now_range == selection_init_range { return; } diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index fdda0e82bc..7a008e3ba2 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -439,17 +439,17 @@ pub fn start_of_excerpt( }; match direction { Direction::Prev => { - let mut start = excerpt.start_anchor().to_display_point(&map); + let mut start = excerpt.start_anchor().to_display_point(map); if start >= display_point && start.row() > DisplayRow(0) { let Some(excerpt) = map.buffer_snapshot.excerpt_before(excerpt.id()) else { return display_point; }; - start = excerpt.start_anchor().to_display_point(&map); + start = excerpt.start_anchor().to_display_point(map); } start } Direction::Next => { - let mut end = excerpt.end_anchor().to_display_point(&map); + let mut end = excerpt.end_anchor().to_display_point(map); *end.row_mut() += 1; map.clip_point(end, Bias::Right) } @@ -467,7 +467,7 @@ pub fn end_of_excerpt( }; match direction { Direction::Prev => { - let mut start = excerpt.start_anchor().to_display_point(&map); + let mut start = excerpt.start_anchor().to_display_point(map); if start.row() > DisplayRow(0) { *start.row_mut() -= 1; } @@ -476,7 +476,7 @@ pub fn end_of_excerpt( start } Direction::Next => { - let mut end = excerpt.end_anchor().to_display_point(&map); + let mut end = excerpt.end_anchor().to_display_point(map); *end.column_mut() = 0; if end <= display_point { *end.row_mut() += 1; @@ -485,7 +485,7 @@ pub fn end_of_excerpt( else { return display_point; }; - end = excerpt.end_anchor().to_display_point(&map); + end = excerpt.end_anchor().to_display_point(map); *end.column_mut() = 0; } end @@ -510,10 +510,10 @@ pub fn find_preceding_boundary_point( if find_range == FindRange::SingleLine && ch == '\n' { break; } - if let Some(prev_ch) = prev_ch { - if is_boundary(ch, prev_ch) { - break; - } + if let Some(prev_ch) = prev_ch + && is_boundary(ch, prev_ch) + { + break; } offset -= ch.len_utf8(); @@ -562,13 +562,13 @@ pub fn find_boundary_point( if find_range == FindRange::SingleLine && ch == '\n' { break; } - if let Some(prev_ch) = prev_ch { - if is_boundary(prev_ch, ch) { - if return_point_before_boundary { - return map.clip_point(prev_offset.to_display_point(map), Bias::Right); - } else { - break; - } + if let Some(prev_ch) = prev_ch + && is_boundary(prev_ch, ch) + { + if return_point_before_boundary { + return map.clip_point(prev_offset.to_display_point(map), Bias::Right); + } else { + break; } } prev_offset = offset; @@ -603,13 +603,13 @@ pub fn find_preceding_boundary_trail( // Find the boundary let start_offset = offset; for ch in forward { - if let Some(prev_ch) = prev_ch { - if is_boundary(prev_ch, ch) { - if start_offset == offset { - trail_offset = Some(offset); - } else { - break; - } + if let Some(prev_ch) = prev_ch + && is_boundary(prev_ch, ch) + { + if start_offset == offset { + trail_offset = Some(offset); + } else { + break; } } offset -= ch.len_utf8(); @@ -651,13 +651,13 @@ pub fn find_boundary_trail( // Find the boundary let start_offset = offset; for ch in forward { - if let Some(prev_ch) = prev_ch { - if is_boundary(prev_ch, ch) { - if start_offset == offset { - trail_offset = Some(offset); - } else { - break; - } + if let Some(prev_ch) = prev_ch + && is_boundary(prev_ch, ch) + { + if start_offset == offset { + trail_offset = Some(offset); + } else { + break; } } offset += ch.len_utf8(); diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index 1ead45b3de..c79feccb4b 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -241,24 +241,13 @@ impl ProposedChangesEditor { event: &BufferEvent, _cx: &mut Context<Self>, ) { - match event { - BufferEvent::Operation { .. } => { - self.recalculate_diffs_tx - .unbounded_send(RecalculateDiff { - buffer, - debounce: true, - }) - .ok(); - } - // BufferEvent::DiffBaseChanged => { - // self.recalculate_diffs_tx - // .unbounded_send(RecalculateDiff { - // buffer, - // debounce: false, - // }) - // .ok(); - // } - _ => (), + if let BufferEvent::Operation { .. } = event { + self.recalculate_diffs_tx + .unbounded_send(RecalculateDiff { + buffer, + debounce: true, + }) + .ok(); } } } @@ -478,7 +467,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider { } fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool { - if let Some(buffer) = self.to_base(&buffer, &[], cx) { + if let Some(buffer) = self.to_base(buffer, &[], cx) { self.0.supports_inlay_hints(&buffer, cx) } else { false @@ -491,7 +480,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider { position: text::Anchor, cx: &mut App, ) -> Option<Task<anyhow::Result<Vec<project::DocumentHighlight>>>> { - let buffer = self.to_base(&buffer, &[position], cx)?; + let buffer = self.to_base(buffer, &[position], cx)?; self.0.document_highlights(&buffer, position, cx) } @@ -502,7 +491,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider { kind: crate::GotoDefinitionKind, cx: &mut App, ) -> Option<Task<anyhow::Result<Vec<project::LocationLink>>>> { - let buffer = self.to_base(&buffer, &[position], cx)?; + let buffer = self.to_base(buffer, &[position], cx)?; self.0.definitions(&buffer, position, kind, cx) } diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index 2b8150de67..e3d83ab160 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -35,12 +35,12 @@ pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: & .filter_map(|buffer| buffer.read(cx).language()) .any(|language| is_rust_language(language)) { - register_action(&editor, window, go_to_parent_module); - register_action(&editor, window, expand_macro_recursively); - register_action(&editor, window, open_docs); - register_action(&editor, window, cancel_flycheck_action); - register_action(&editor, window, run_flycheck_action); - register_action(&editor, window, clear_flycheck_action); + register_action(editor, window, go_to_parent_module); + register_action(editor, window, expand_macro_recursively); + register_action(editor, window, open_docs); + register_action(editor, window, cancel_flycheck_action); + register_action(editor, window, run_flycheck_action); + register_action(editor, window, clear_flycheck_action); } } @@ -285,11 +285,11 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu workspace.update(cx, |_workspace, cx| { // Check if the local document exists, otherwise fallback to the online document. // Open with the default browser. - if let Some(local_url) = docs_urls.local { - if fs::metadata(Path::new(&local_url[8..])).is_ok() { - cx.open_url(&local_url); - return; - } + if let Some(local_url) = docs_urls.local + && fs::metadata(Path::new(&local_url[8..])).is_ok() + { + cx.open_url(&local_url); + return; } if let Some(web_url) = docs_urls.web { diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 3e8f7741f7..c2bcdaf68f 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -699,20 +699,20 @@ impl Editor { if matches!( settings.defaults.soft_wrap, SoftWrap::PreferredLineLength | SoftWrap::Bounded - ) { - if (settings.defaults.preferred_line_length as f32) < visible_column_count { - visible_column_count = settings.defaults.preferred_line_length as f32; - } + ) && (settings.defaults.preferred_line_length as f32) < visible_column_count + { + visible_column_count = settings.defaults.preferred_line_length as f32; } // If the scroll position is currently at the left edge of the document // (x == 0.0) and the intent is to scroll right, the gutter's margin // should first be added to the current position, otherwise the cursor // will end at the column position minus the margin, which looks off. - if current_position.x == 0.0 && amount.columns(visible_column_count) > 0. { - if let Some(last_position_map) = &self.last_position_map { - current_position.x += self.gutter_dimensions.margin / last_position_map.em_advance; - } + if current_position.x == 0.0 + && amount.columns(visible_column_count) > 0. + && let Some(last_position_map) = &self.last_position_map + { + current_position.x += self.gutter_dimensions.margin / last_position_map.em_advance; } let new_position = current_position + point( @@ -745,12 +745,10 @@ impl Editor { if let (Some(visible_lines), Some(visible_columns)) = (self.visible_line_count(), self.visible_column_count()) + && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32) + && newest_head.column() <= screen_top.column() + visible_columns as u32 { - if newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32) - && newest_head.column() <= screen_top.column() + visible_columns as u32 - { - return Ordering::Equal; - } + return Ordering::Equal; } Ordering::Greater diff --git a/crates/editor/src/scroll/autoscroll.rs b/crates/editor/src/scroll/autoscroll.rs index 88d3b52d76..057d622903 100644 --- a/crates/editor/src/scroll/autoscroll.rs +++ b/crates/editor/src/scroll/autoscroll.rs @@ -116,12 +116,12 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut scroll_position = self.scroll_manager.scroll_position(&display_map); let original_y = scroll_position.y; - if let Some(last_bounds) = self.expect_bounds_change.take() { - if scroll_position.y != 0. { - scroll_position.y += (bounds.top() - last_bounds.top()) / line_height; - if scroll_position.y < 0. { - scroll_position.y = 0.; - } + if let Some(last_bounds) = self.expect_bounds_change.take() + && scroll_position.y != 0. + { + scroll_position.y += (bounds.top() - last_bounds.top()) / line_height; + if scroll_position.y < 0. { + scroll_position.y = 0.; } } if scroll_position.y > max_scroll_top { diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 73c5f1c076..0a02390b64 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -119,8 +119,8 @@ impl SelectionsCollection { cx: &mut App, ) -> Option<Selection<D>> { let map = self.display_map(cx); - let selection = resolve_selections(self.pending_anchor().as_ref(), &map).next(); - selection + + resolve_selections(self.pending_anchor().as_ref(), &map).next() } pub(crate) fn pending_mode(&self) -> Option<SelectMode> { @@ -276,18 +276,18 @@ impl SelectionsCollection { cx: &mut App, ) -> Selection<D> { let map = self.display_map(cx); - let selection = resolve_selections([self.newest_anchor()], &map) + + resolve_selections([self.newest_anchor()], &map) .next() - .unwrap(); - selection + .unwrap() } pub fn newest_display(&self, cx: &mut App) -> Selection<DisplayPoint> { let map = self.display_map(cx); - let selection = resolve_selections_display([self.newest_anchor()], &map) + + resolve_selections_display([self.newest_anchor()], &map) .next() - .unwrap(); - selection + .unwrap() } pub fn oldest_anchor(&self) -> &Selection<Anchor> { @@ -303,10 +303,10 @@ impl SelectionsCollection { cx: &mut App, ) -> Selection<D> { let map = self.display_map(cx); - let selection = resolve_selections([self.oldest_anchor()], &map) + + resolve_selections([self.oldest_anchor()], &map) .next() - .unwrap(); - selection + .unwrap() } pub fn first_anchor(&self) -> Selection<Anchor> { diff --git a/crates/editor/src/signature_help.rs b/crates/editor/src/signature_help.rs index d242e886c2..ab5e823f1c 100644 --- a/crates/editor/src/signature_help.rs +++ b/crates/editor/src/signature_help.rs @@ -196,7 +196,7 @@ impl Editor { .highlight_text(&text, 0..signature.label.len()) .into_iter() .flat_map(|(range, highlight_id)| { - Some((range, highlight_id.style(&cx.theme().syntax())?)) + Some((range, highlight_id.style(cx.theme().syntax())?)) }); signature.highlights = combine_highlights(signature.highlights.clone(), highlights) diff --git a/crates/editor/src/tasks.rs b/crates/editor/src/tasks.rs index 0d497e4cac..8be2a3a2e1 100644 --- a/crates/editor/src/tasks.rs +++ b/crates/editor/src/tasks.rs @@ -89,7 +89,7 @@ impl Editor { .lsp_task_source()?; if lsp_settings .get(&lsp_tasks_source) - .map_or(true, |s| s.enable_lsp_tasks) + .is_none_or(|s| s.enable_lsp_tasks) { let buffer_id = buffer.read(cx).remote_id(); Some((lsp_tasks_source, buffer_id)) diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index f328945dbe..d388e8f3b7 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -184,12 +184,12 @@ pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestCo for (row, block) in blocks { match block { Block::Custom(custom_block) => { - if let BlockPlacement::Near(x) = &custom_block.placement { - if snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot)) { - continue; - } + if let BlockPlacement::Near(x) = &custom_block.placement + && snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot)) + { + continue; }; - let content = block_content_for_tests(&editor, custom_block.id, cx) + let content = block_content_for_tests(editor, custom_block.id, cx) .expect("block content not found"); // 2: "related info 1 for diagnostic 0" if let Some(height) = custom_block.height { diff --git a/crates/eval/src/assertions.rs b/crates/eval/src/assertions.rs index 489e4aa22e..01fac186d3 100644 --- a/crates/eval/src/assertions.rs +++ b/crates/eval/src/assertions.rs @@ -54,7 +54,7 @@ impl AssertionsReport { pub fn passed_count(&self) -> usize { self.ran .iter() - .filter(|a| a.result.as_ref().map_or(false, |result| result.passed)) + .filter(|a| a.result.as_ref().is_ok_and(|result| result.passed)) .count() } diff --git a/crates/eval/src/eval.rs b/crates/eval/src/eval.rs index 6558222d89..1d2bece5cc 100644 --- a/crates/eval/src/eval.rs +++ b/crates/eval/src/eval.rs @@ -112,7 +112,7 @@ fn main() { let telemetry = app_state.client.telemetry(); telemetry.start(system_id, installation_id, session_id, cx); - let enable_telemetry = env::var("ZED_EVAL_TELEMETRY").map_or(false, |value| value == "1") + let enable_telemetry = env::var("ZED_EVAL_TELEMETRY").is_ok_and(|value| value == "1") && telemetry.has_checksum_seed(); if enable_telemetry { println!("Telemetry enabled"); @@ -167,15 +167,14 @@ fn main() { continue; } - if let Some(language) = meta.language_server { - if !languages.contains(&language.file_extension) { + if let Some(language) = meta.language_server + && !languages.contains(&language.file_extension) { panic!( "Eval for {:?} could not be run because no language server was found for extension {:?}", meta.name, language.file_extension ); } - } // TODO: This creates a worktree per repetition. Ideally these examples should // either be run sequentially on the same worktree, or reuse worktrees when there @@ -520,7 +519,7 @@ async fn judge_example( enable_telemetry: bool, cx: &AsyncApp, ) -> JudgeOutput { - let judge_output = example.judge(model.clone(), &run_output, cx).await; + let judge_output = example.judge(model.clone(), run_output, cx).await; if enable_telemetry { telemetry::event!( diff --git a/crates/eval/src/example.rs b/crates/eval/src/example.rs index 23c8814916..82e95728a1 100644 --- a/crates/eval/src/example.rs +++ b/crates/eval/src/example.rs @@ -64,7 +64,7 @@ impl ExampleMetadata { self.url .split('/') .next_back() - .unwrap_or(&"") + .unwrap_or("") .trim_end_matches(".git") .into() } @@ -255,7 +255,7 @@ impl ExampleContext { thread.update(cx, |thread, _cx| { if let Some(tool_use) = pending_tool_use { let mut tool_metrics = tool_metrics.lock().unwrap(); - if let Some(tool_result) = thread.tool_result(&tool_use_id) { + if let Some(tool_result) = thread.tool_result(tool_use_id) { let message = if tool_result.is_error { format!("✖︎ {}", tool_use.name) } else { diff --git a/crates/eval/src/examples/add_arg_to_trait_method.rs b/crates/eval/src/examples/add_arg_to_trait_method.rs index 9c538f9260..084f12bc62 100644 --- a/crates/eval/src/examples/add_arg_to_trait_method.rs +++ b/crates/eval/src/examples/add_arg_to_trait_method.rs @@ -70,10 +70,10 @@ impl Example for AddArgToTraitMethod { let path_str = format!("crates/assistant_tools/src/{}.rs", tool_name); let edits = edits.get(Path::new(&path_str)); - let ignored = edits.map_or(false, |edits| { + let ignored = edits.is_some_and(|edits| { edits.has_added_line(" _window: Option<gpui::AnyWindowHandle>,\n") }); - let uningored = edits.map_or(false, |edits| { + let uningored = edits.is_some_and(|edits| { edits.has_added_line(" window: Option<gpui::AnyWindowHandle>,\n") }); @@ -89,7 +89,7 @@ impl Example for AddArgToTraitMethod { let batch_tool_edits = edits.get(Path::new("crates/assistant_tools/src/batch_tool.rs")); cx.assert( - batch_tool_edits.map_or(false, |edits| { + batch_tool_edits.is_some_and(|edits| { edits.has_added_line(" window: Option<gpui::AnyWindowHandle>,\n") }), "Argument: batch_tool", diff --git a/crates/eval/src/explorer.rs b/crates/eval/src/explorer.rs index ee1dfa95c3..3326070cea 100644 --- a/crates/eval/src/explorer.rs +++ b/crates/eval/src/explorer.rs @@ -46,27 +46,25 @@ fn find_target_files_recursive( max_depth, found_files, )?; - } else if path.is_file() { - if let Some(filename_osstr) = path.file_name() { - if let Some(filename_str) = filename_osstr.to_str() { - if filename_str == target_filename { - found_files.push(path); - } - } - } + } else if path.is_file() + && let Some(filename_osstr) = path.file_name() + && let Some(filename_str) = filename_osstr.to_str() + && filename_str == target_filename + { + found_files.push(path); } } Ok(()) } pub fn generate_explorer_html(input_paths: &[PathBuf], output_path: &PathBuf) -> Result<String> { - if let Some(parent) = output_path.parent() { - if !parent.exists() { - fs::create_dir_all(parent).context(format!( - "Failed to create output directory: {}", - parent.display() - ))?; - } + if let Some(parent) = output_path.parent() + && !parent.exists() + { + fs::create_dir_all(parent).context(format!( + "Failed to create output directory: {}", + parent.display() + ))?; } let template_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/explorer.html"); diff --git a/crates/eval/src/instance.rs b/crates/eval/src/instance.rs index 0f2b4c18ea..bbbe54b43f 100644 --- a/crates/eval/src/instance.rs +++ b/crates/eval/src/instance.rs @@ -376,11 +376,10 @@ impl ExampleInstance { ); let result = this.thread.conversation(&mut example_cx).await; - if let Err(err) = result { - if !err.is::<FailedAssertion>() { + if let Err(err) = result + && !err.is::<FailedAssertion>() { return Err(err); } - } println!("{}Stopped", this.log_prefix); @@ -459,8 +458,8 @@ impl ExampleInstance { let mut output_file = File::create(self.run_directory.join("judge.md")).expect("failed to create judge.md"); - let diff_task = self.judge_diff(model.clone(), &run_output, cx); - let thread_task = self.judge_thread(model.clone(), &run_output, cx); + let diff_task = self.judge_diff(model.clone(), run_output, cx); + let thread_task = self.judge_thread(model.clone(), run_output, cx); let (diff_result, thread_result) = futures::join!(diff_task, thread_task); @@ -661,7 +660,7 @@ pub fn wait_for_lang_server( .update(cx, |buffer, cx| { lsp_store.update(cx, |lsp_store, cx| { lsp_store - .language_servers_for_local_buffer(&buffer, cx) + .language_servers_for_local_buffer(buffer, cx) .next() .is_some() }) @@ -679,8 +678,8 @@ pub fn wait_for_lang_server( [ cx.subscribe(&lsp_store, { let log_prefix = log_prefix.clone(); - move |_, event, _| match event { - project::LspStoreEvent::LanguageServerUpdate { + move |_, event, _| { + if let project::LspStoreEvent::LanguageServerUpdate { message: client::proto::update_language_server::Variant::WorkProgress( LspWorkProgress { @@ -689,11 +688,13 @@ pub fn wait_for_lang_server( }, ), .. - } => println!("{}⟲ {message}", log_prefix), - _ => {} + } = event + { + println!("{}⟲ {message}", log_prefix) + } } }), - cx.subscribe(&project, { + cx.subscribe(project, { let buffer = buffer.clone(); move |project, event, cx| match event { project::Event::LanguageServerAdded(_, _, _) => { @@ -838,7 +839,7 @@ fn messages_to_markdown<'a>(message_iter: impl IntoIterator<Item = &'a Message>) for segment in &message.segments { match segment { MessageSegment::Text(text) => { - messages.push_str(&text); + messages.push_str(text); messages.push_str("\n\n"); } MessageSegment::Thinking { text, signature } => { @@ -846,7 +847,7 @@ fn messages_to_markdown<'a>(message_iter: impl IntoIterator<Item = &'a Message>) if let Some(sig) = signature { messages.push_str(&format!("Signature: {}\n\n", sig)); } - messages.push_str(&text); + messages.push_str(text); messages.push_str("\n"); } MessageSegment::RedactedThinking(items) => { @@ -878,7 +879,7 @@ pub async fn send_language_model_request( request: LanguageModelRequest, cx: &AsyncApp, ) -> anyhow::Result<String> { - match model.stream_completion_text(request, &cx).await { + match model.stream_completion_text(request, cx).await { Ok(mut stream) => { let mut full_response = String::new(); while let Some(chunk_result) = stream.stream.next().await { diff --git a/crates/extension/src/extension.rs b/crates/extension/src/extension.rs index 35f7f41938..6af793253b 100644 --- a/crates/extension/src/extension.rs +++ b/crates/extension/src/extension.rs @@ -178,16 +178,15 @@ pub fn parse_wasm_extension_version( for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) { if let wasmparser::Payload::CustomSection(s) = part.context("error parsing wasm extension")? + && s.name() == "zed:api-version" { - if s.name() == "zed:api-version" { - version = parse_wasm_extension_version_custom_section(s.data()); - if version.is_none() { - bail!( - "extension {} has invalid zed:api-version section: {:?}", - extension_id, - s.data() - ); - } + version = parse_wasm_extension_version_custom_section(s.data()); + if version.is_none() { + bail!( + "extension {} has invalid zed:api-version section: {:?}", + extension_id, + s.data() + ); } } } diff --git a/crates/extension/src/extension_builder.rs b/crates/extension/src/extension_builder.rs index 621ba9250c..3a3026f19c 100644 --- a/crates/extension/src/extension_builder.rs +++ b/crates/extension/src/extension_builder.rs @@ -401,7 +401,7 @@ impl ExtensionBuilder { let mut clang_path = wasi_sdk_dir.clone(); clang_path.extend(["bin", &format!("clang{}", env::consts::EXE_SUFFIX)]); - if fs::metadata(&clang_path).map_or(false, |metadata| metadata.is_file()) { + if fs::metadata(&clang_path).is_ok_and(|metadata| metadata.is_file()) { return Ok(clang_path); } @@ -452,7 +452,7 @@ impl ExtensionBuilder { let mut output = Vec::new(); let mut stack = Vec::new(); - for payload in Parser::new(0).parse_all(&input) { + for payload in Parser::new(0).parse_all(input) { let payload = payload?; // Track nesting depth, so that we don't mess with inner producer sections: @@ -484,14 +484,10 @@ impl ExtensionBuilder { _ => {} } - match &payload { - CustomSection(c) => { - if strip_custom_section(c.name()) { - continue; - } - } - - _ => {} + if let CustomSection(c) = &payload + && strip_custom_section(c.name()) + { + continue; } if let Some((id, range)) = payload.as_section() { RawSection { diff --git a/crates/extension/src/extension_events.rs b/crates/extension/src/extension_events.rs index b151b3f412..94f3277b05 100644 --- a/crates/extension/src/extension_events.rs +++ b/crates/extension/src/extension_events.rs @@ -19,9 +19,8 @@ pub struct ExtensionEvents; impl ExtensionEvents { /// Returns the global [`ExtensionEvents`]. pub fn try_global(cx: &App) -> Option<Entity<Self>> { - return cx - .try_global::<GlobalExtensionEvents>() - .map(|g| g.0.clone()); + cx.try_global::<GlobalExtensionEvents>() + .map(|g| g.0.clone()) } fn new(_cx: &mut Context<Self>) -> Self { diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index e795fa5ac5..4c3ab8d242 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -93,10 +93,9 @@ pub fn is_version_compatible( .wasm_api_version .as_ref() .and_then(|wasm_api_version| SemanticVersion::from_str(wasm_api_version).ok()) + && !is_supported_wasm_api_version(release_channel, wasm_api_version) { - if !is_supported_wasm_api_version(release_channel, wasm_api_version) { - return false; - } + return false; } true @@ -292,19 +291,17 @@ impl ExtensionStore { // it must be asynchronously rebuilt. let mut extension_index = ExtensionIndex::default(); let mut extension_index_needs_rebuild = true; - if let Ok(index_content) = index_content { - if let Some(index) = serde_json::from_str(&index_content).log_err() { - extension_index = index; - if let (Ok(Some(index_metadata)), Ok(Some(extensions_metadata))) = - (index_metadata, extensions_metadata) - { - if index_metadata - .mtime - .bad_is_greater_than(extensions_metadata.mtime) - { - extension_index_needs_rebuild = false; - } - } + if let Ok(index_content) = index_content + && let Some(index) = serde_json::from_str(&index_content).log_err() + { + extension_index = index; + if let (Ok(Some(index_metadata)), Ok(Some(extensions_metadata))) = + (index_metadata, extensions_metadata) + && index_metadata + .mtime + .bad_is_greater_than(extensions_metadata.mtime) + { + extension_index_needs_rebuild = false; } } @@ -392,10 +389,9 @@ impl ExtensionStore { if let Some(path::Component::Normal(extension_dir_name)) = event_path.components().next() + && let Some(extension_id) = extension_dir_name.to_str() { - if let Some(extension_id) = extension_dir_name.to_str() { - reload_tx.unbounded_send(Some(extension_id.into())).ok(); - } + reload_tx.unbounded_send(Some(extension_id.into())).ok(); } } } @@ -566,12 +562,12 @@ impl ExtensionStore { extensions .into_iter() .filter(|extension| { - this.extension_index.extensions.get(&extension.id).map_or( - true, - |installed_extension| { + this.extension_index + .extensions + .get(&extension.id) + .is_none_or(|installed_extension| { installed_extension.manifest.version != extension.manifest.version - }, - ) + }) }) .collect() }) @@ -763,8 +759,8 @@ impl ExtensionStore { if let ExtensionOperation::Install = operation { this.update( cx, |this, cx| { cx.emit(Event::ExtensionInstalled(extension_id.clone())); - if let Some(events) = ExtensionEvents::try_global(cx) { - if let Some(manifest) = this.extension_manifest_for_id(&extension_id) { + if let Some(events) = ExtensionEvents::try_global(cx) + && let Some(manifest) = this.extension_manifest_for_id(&extension_id) { events.update(cx, |this, cx| { this.emit( extension::Event::ExtensionInstalled(manifest.clone()), @@ -772,7 +768,6 @@ impl ExtensionStore { ) }); } - } }) .ok(); } @@ -912,12 +907,12 @@ impl ExtensionStore { extension_store.update(cx, |_, cx| { cx.emit(Event::ExtensionUninstalled(extension_id.clone())); - if let Some(events) = ExtensionEvents::try_global(cx) { - if let Some(manifest) = extension_manifest { - events.update(cx, |this, cx| { - this.emit(extension::Event::ExtensionUninstalled(manifest.clone()), cx) - }); - } + if let Some(events) = ExtensionEvents::try_global(cx) + && let Some(manifest) = extension_manifest + { + events.update(cx, |this, cx| { + this.emit(extension::Event::ExtensionUninstalled(manifest.clone()), cx) + }); } })?; @@ -997,12 +992,12 @@ impl ExtensionStore { this.update(cx, |this, cx| this.reload(None, cx))?.await; this.update(cx, |this, cx| { cx.emit(Event::ExtensionInstalled(extension_id.clone())); - if let Some(events) = ExtensionEvents::try_global(cx) { - if let Some(manifest) = this.extension_manifest_for_id(&extension_id) { - events.update(cx, |this, cx| { - this.emit(extension::Event::ExtensionInstalled(manifest.clone()), cx) - }); - } + if let Some(events) = ExtensionEvents::try_global(cx) + && let Some(manifest) = this.extension_manifest_for_id(&extension_id) + { + events.update(cx, |this, cx| { + this.emit(extension::Event::ExtensionInstalled(manifest.clone()), cx) + }); } })?; @@ -1341,7 +1336,7 @@ impl ExtensionStore { &extension_path, &extension.manifest, wasm_host.clone(), - &cx, + cx, ) .await .with_context(|| format!("Loading extension from {extension_path:?}")); @@ -1456,7 +1451,7 @@ impl ExtensionStore { if extension_dir .file_name() - .map_or(false, |file_name| file_name == ".DS_Store") + .is_some_and(|file_name| file_name == ".DS_Store") { continue; } @@ -1680,9 +1675,8 @@ impl ExtensionStore { let schema_path = &extension::build_debug_adapter_schema_path(adapter_name, meta); if fs.is_file(&src_dir.join(schema_path)).await { - match schema_path.parent() { - Some(parent) => fs.create_dir(&tmp_dir.join(parent)).await?, - None => {} + if let Some(parent) = schema_path.parent() { + fs.create_dir(&tmp_dir.join(parent)).await? } fs.copy_file( &src_dir.join(schema_path), @@ -1776,7 +1770,7 @@ impl ExtensionStore { })?; for client in clients { - Self::sync_extensions_over_ssh(&this, client, cx) + Self::sync_extensions_over_ssh(this, client, cx) .await .log_err(); } @@ -1788,10 +1782,10 @@ impl ExtensionStore { let connection_options = client.read(cx).connection_options(); let ssh_url = connection_options.ssh_url(); - if let Some(existing_client) = self.ssh_clients.get(&ssh_url) { - if existing_client.upgrade().is_some() { - return; - } + if let Some(existing_client) = self.ssh_clients.get(&ssh_url) + && existing_client.upgrade().is_some() + { + return; } self.ssh_clients.insert(ssh_url, client.downgrade()); diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs index 8ce3847376..a6305118cd 100644 --- a/crates/extension_host/src/headless_host.rs +++ b/crates/extension_host/src/headless_host.rs @@ -175,7 +175,7 @@ impl HeadlessExtensionStore { } let wasm_extension: Arc<dyn Extension> = - Arc::new(WasmExtension::load(&extension_dir, &manifest, wasm_host.clone(), &cx).await?); + Arc::new(WasmExtension::load(&extension_dir, &manifest, wasm_host.clone(), cx).await?); for (language_server_id, language_server_config) in &manifest.language_servers { for language in language_server_config.languages() { diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index d990b670f4..c5bc21fc1c 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -532,7 +532,7 @@ fn wasm_engine(executor: &BackgroundExecutor) -> wasmtime::Engine { // `Future::poll`. const EPOCH_INTERVAL: Duration = Duration::from_millis(100); let mut timer = Timer::interval(EPOCH_INTERVAL); - while let Some(_) = timer.next().await { + while (timer.next().await).is_some() { // Exit the loop and thread once the engine is dropped. let Some(engine) = engine_ref.upgrade() else { break; @@ -701,16 +701,15 @@ pub fn parse_wasm_extension_version( for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) { if let wasmparser::Payload::CustomSection(s) = part.context("error parsing wasm extension")? + && s.name() == "zed:api-version" { - if s.name() == "zed:api-version" { - version = parse_wasm_extension_version_custom_section(s.data()); - if version.is_none() { - bail!( - "extension {} has invalid zed:api-version section: {:?}", - extension_id, - s.data() - ); - } + version = parse_wasm_extension_version_custom_section(s.data()); + if version.is_none() { + bail!( + "extension {} has invalid zed:api-version section: {:?}", + extension_id, + s.data() + ); } } } diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index f63287f55a..10a29b4367 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -116,6 +116,7 @@ pub fn init(cx: &mut App) { files: false, directories: true, multiple: false, + prompt: None, }, DirectoryLister::Local( workspace.project().clone(), @@ -860,7 +861,7 @@ impl ExtensionsPage { window: &mut Window, cx: &mut App, ) -> Entity<ContextMenu> { - let context_menu = ContextMenu::build(window, cx, |context_menu, window, _| { + ContextMenu::build(window, cx, |context_menu, window, _| { context_menu .entry( "Install Another Version...", @@ -884,9 +885,7 @@ impl ExtensionsPage { cx.write_to_clipboard(ClipboardItem::new_string(authors.join(", "))); } }) - }); - - context_menu + }) } fn show_extension_version_list( @@ -1028,15 +1027,14 @@ impl ExtensionsPage { .read(cx) .extension_manifest_for_id(&extension_id) .cloned() + && let Some(events) = extension::ExtensionEvents::try_global(cx) { - if let Some(events) = extension::ExtensionEvents::try_global(cx) { - events.update(cx, |this, cx| { - this.emit( - extension::Event::ConfigureExtensionRequested(manifest), - cx, - ) - }); - } + events.update(cx, |this, cx| { + this.emit( + extension::Event::ConfigureExtensionRequested(manifest), + cx, + ) + }); } } }) diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index ef357adf35..422979c429 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -14,7 +14,7 @@ struct FeatureFlags { } pub static ZED_DISABLE_STAFF: LazyLock<bool> = LazyLock::new(|| { - std::env::var("ZED_DISABLE_STAFF").map_or(false, |value| !value.is_empty() && value != "0") + std::env::var("ZED_DISABLE_STAFF").is_ok_and(|value| !value.is_empty() && value != "0") }); impl FeatureFlags { @@ -77,14 +77,6 @@ impl FeatureFlag for NotebookFeatureFlag { const NAME: &'static str = "notebooks"; } -pub struct ThreadAutoCaptureFeatureFlag {} -impl FeatureFlag for ThreadAutoCaptureFeatureFlag { - const NAME: &'static str = "thread-auto-capture"; - - fn enabled_for_staff() -> bool { - false - } -} pub struct PanicFeatureFlag; impl FeatureFlag for PanicFeatureFlag { @@ -97,10 +89,21 @@ impl FeatureFlag for JjUiFeatureFlag { const NAME: &'static str = "jj-ui"; } -pub struct AcpFeatureFlag; +pub struct GeminiAndNativeFeatureFlag; -impl FeatureFlag for AcpFeatureFlag { - const NAME: &'static str = "acp"; +impl FeatureFlag for GeminiAndNativeFeatureFlag { + // This was previously called "acp". + // + // We renamed it because existing builds used it to enable the Claude Code + // integration too, and we'd like to turn Gemini/Native on in new builds + // without enabling Claude Code in old builds. + const NAME: &'static str = "gemini-and-native"; +} + +pub struct ClaudeCodeFeatureFlag; + +impl FeatureFlag for ClaudeCodeFeatureFlag { + const NAME: &'static str = "claude-code"; } pub trait FeatureFlagViewExt<V: 'static> { diff --git a/crates/feedback/src/system_specs.rs b/crates/feedback/src/system_specs.rs index 7c002d90e9..b5ccaca689 100644 --- a/crates/feedback/src/system_specs.rs +++ b/crates/feedback/src/system_specs.rs @@ -135,7 +135,7 @@ impl Display for SystemSpecs { fn try_determine_available_gpus() -> Option<String> { #[cfg(any(target_os = "linux", target_os = "freebsd"))] { - return std::process::Command::new("vulkaninfo") + std::process::Command::new("vulkaninfo") .args(&["--summary"]) .output() .ok() @@ -150,11 +150,11 @@ fn try_determine_available_gpus() -> Option<String> { ] .join("\n") }) - .or(Some("Failed to run `vulkaninfo --summary`".to_string())); + .or(Some("Failed to run `vulkaninfo --summary`".to_string())) } #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] { - return None; + None } } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index c6997ccdc0..3a08ec08e0 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -209,11 +209,11 @@ impl FileFinder { let Some(init_modifiers) = self.init_modifiers.take() else { return; }; - if self.picker.read(cx).delegate.has_changed_selected_index { - if !event.modified() || !init_modifiers.is_subset_of(&event) { - self.init_modifiers = None; - window.dispatch_action(menu::Confirm.boxed_clone(), cx); - } + if self.picker.read(cx).delegate.has_changed_selected_index + && (!event.modified() || !init_modifiers.is_subset_of(event)) + { + self.init_modifiers = None; + window.dispatch_action(menu::Confirm.boxed_clone(), cx); } } @@ -323,27 +323,27 @@ impl FileFinder { ) { self.picker.update(cx, |picker, cx| { let delegate = &mut picker.delegate; - if let Some(workspace) = delegate.workspace.upgrade() { - if let Some(m) = delegate.matches.get(delegate.selected_index()) { - let path = match &m { - Match::History { path, .. } => { - let worktree_id = path.project.worktree_id; - ProjectPath { - worktree_id, - path: Arc::clone(&path.project.path), - } + if let Some(workspace) = delegate.workspace.upgrade() + && let Some(m) = delegate.matches.get(delegate.selected_index()) + { + let path = match &m { + Match::History { path, .. } => { + let worktree_id = path.project.worktree_id; + ProjectPath { + worktree_id, + path: Arc::clone(&path.project.path), } - Match::Search(m) => ProjectPath { - worktree_id: WorktreeId::from_usize(m.0.worktree_id), - path: m.0.path.clone(), - }, - Match::CreateNew(p) => p.clone(), - }; - let open_task = workspace.update(cx, move |workspace, cx| { - workspace.split_path_preview(path, false, Some(split_direction), window, cx) - }); - open_task.detach_and_log_err(cx); - } + } + Match::Search(m) => ProjectPath { + worktree_id: WorktreeId::from_usize(m.0.worktree_id), + path: m.0.path.clone(), + }, + Match::CreateNew(p) => p.clone(), + }; + let open_task = workspace.update(cx, move |workspace, cx| { + workspace.split_path_preview(path, false, Some(split_direction), window, cx) + }); + open_task.detach_and_log_err(cx); } }) } @@ -497,7 +497,7 @@ impl Match { fn panel_match(&self) -> Option<&ProjectPanelOrdMatch> { match self { Match::History { panel_match, .. } => panel_match.as_ref(), - Match::Search(panel_match) => Some(&panel_match), + Match::Search(panel_match) => Some(panel_match), Match::CreateNew(_) => None, } } @@ -537,7 +537,7 @@ impl Matches { self.matches.binary_search_by(|m| { // `reverse()` since if cmp_matches(a, b) == Ordering::Greater, then a is better than b. // And we want the better entries go first. - Self::cmp_matches(self.separate_history, currently_opened, &m, &entry).reverse() + Self::cmp_matches(self.separate_history, currently_opened, m, entry).reverse() }) } } @@ -675,17 +675,17 @@ impl Matches { let path_str = panel_match.0.path.to_string_lossy(); let filename_str = filename.to_string_lossy(); - if let Some(filename_pos) = path_str.rfind(&*filename_str) { - if panel_match.0.positions[0] >= filename_pos { - let mut prev_position = panel_match.0.positions[0]; - for p in &panel_match.0.positions[1..] { - if *p != prev_position + 1 { - return false; - } - prev_position = *p; + if let Some(filename_pos) = path_str.rfind(&*filename_str) + && panel_match.0.positions[0] >= filename_pos + { + let mut prev_position = panel_match.0.positions[0]; + for p in &panel_match.0.positions[1..] { + if *p != prev_position + 1 { + return false; } - return true; + prev_position = *p; } + return true; } } @@ -878,9 +878,7 @@ impl FileFinderDelegate { PathMatchCandidateSet { snapshot: worktree.snapshot(), include_ignored: self.include_ignored.unwrap_or_else(|| { - worktree - .root_entry() - .map_or(false, |entry| entry.is_ignored) + worktree.root_entry().is_some_and(|entry| entry.is_ignored) }), include_root_name, candidates: project::Candidates::Files, @@ -1045,10 +1043,10 @@ impl FileFinderDelegate { ) } else { let mut path = Arc::clone(project_relative_path); - if project_relative_path.as_ref() == Path::new("") { - if let Some(absolute_path) = &entry_path.absolute { - path = Arc::from(absolute_path.as_path()); - } + if project_relative_path.as_ref() == Path::new("") + && let Some(absolute_path) = &entry_path.absolute + { + path = Arc::from(absolute_path.as_path()); } let mut path_match = PathMatch { @@ -1078,23 +1076,21 @@ impl FileFinderDelegate { ), }; - if file_name_positions.is_empty() { - if let Some(user_home_path) = std::env::var("HOME").ok() { - let user_home_path = user_home_path.trim(); - if !user_home_path.is_empty() { - if (&full_path).starts_with(user_home_path) { - full_path.replace_range(0..user_home_path.len(), "~"); - full_path_positions.retain_mut(|pos| { - if *pos >= user_home_path.len() { - *pos -= user_home_path.len(); - *pos += 1; - true - } else { - false - } - }) + if file_name_positions.is_empty() + && let Some(user_home_path) = std::env::var("HOME").ok() + { + let user_home_path = user_home_path.trim(); + if !user_home_path.is_empty() && full_path.starts_with(user_home_path) { + full_path.replace_range(0..user_home_path.len(), "~"); + full_path_positions.retain_mut(|pos| { + if *pos >= user_home_path.len() { + *pos -= user_home_path.len(); + *pos += 1; + true + } else { + false } - } + }) } } @@ -1242,14 +1238,13 @@ impl FileFinderDelegate { /// Skips first history match (that is displayed topmost) if it's currently opened. fn calculate_selected_index(&self, cx: &mut Context<Picker<Self>>) -> usize { - if FileFinderSettings::get_global(cx).skip_focus_for_active_in_search { - if let Some(Match::History { path, .. }) = self.matches.get(0) { - if Some(path) == self.currently_opened_path.as_ref() { - let elements_after_first = self.matches.len() - 1; - if elements_after_first > 0 { - return 1; - } - } + if FileFinderSettings::get_global(cx).skip_focus_for_active_in_search + && let Some(Match::History { path, .. }) = self.matches.get(0) + && Some(path) == self.currently_opened_path.as_ref() + { + let elements_after_first = self.matches.len() - 1; + if elements_after_first > 0 { + return 1; } } @@ -1310,10 +1305,10 @@ impl PickerDelegate for FileFinderDelegate { .enumerate() .find(|(_, m)| !matches!(m, Match::History { .. })) .map(|(i, _)| i); - if let Some(first_non_history_index) = first_non_history_index { - if first_non_history_index > 0 { - return vec![first_non_history_index - 1]; - } + if let Some(first_non_history_index) = first_non_history_index + && first_non_history_index > 0 + { + return vec![first_non_history_index - 1]; } } Vec::new() @@ -1402,7 +1397,7 @@ impl PickerDelegate for FileFinderDelegate { cx.notify(); Task::ready(()) } else { - let path_position = PathWithPosition::parse_str(&raw_query); + let path_position = PathWithPosition::parse_str(raw_query); #[cfg(windows)] let raw_query = raw_query.trim().to_owned().replace("/", "\\"); @@ -1436,69 +1431,101 @@ impl PickerDelegate for FileFinderDelegate { window: &mut Window, cx: &mut Context<Picker<FileFinderDelegate>>, ) { - if let Some(m) = self.matches.get(self.selected_index()) { - if let Some(workspace) = self.workspace.upgrade() { - let open_task = workspace.update(cx, |workspace, cx| { - let split_or_open = - |workspace: &mut Workspace, - project_path, - window: &mut Window, - cx: &mut Context<Workspace>| { - let allow_preview = - PreviewTabsSettings::get_global(cx).enable_preview_from_file_finder; - if secondary { - workspace.split_path_preview( - project_path, - allow_preview, - None, - window, - cx, - ) - } else { - workspace.open_path_preview( - project_path, - None, - true, - allow_preview, - true, - window, - cx, - ) - } - }; - match &m { - Match::CreateNew(project_path) => { - // Create a new file with the given filename - if secondary { - workspace.split_path_preview( - project_path.clone(), - false, - None, - window, - cx, - ) - } else { - workspace.open_path_preview( - project_path.clone(), - None, - true, - false, - true, - window, - cx, - ) - } + if let Some(m) = self.matches.get(self.selected_index()) + && let Some(workspace) = self.workspace.upgrade() + { + let open_task = workspace.update(cx, |workspace, cx| { + let split_or_open = + |workspace: &mut Workspace, + project_path, + window: &mut Window, + cx: &mut Context<Workspace>| { + let allow_preview = + PreviewTabsSettings::get_global(cx).enable_preview_from_file_finder; + if secondary { + workspace.split_path_preview( + project_path, + allow_preview, + None, + window, + cx, + ) + } else { + workspace.open_path_preview( + project_path, + None, + true, + allow_preview, + true, + window, + cx, + ) } + }; + match &m { + Match::CreateNew(project_path) => { + // Create a new file with the given filename + if secondary { + workspace.split_path_preview( + project_path.clone(), + false, + None, + window, + cx, + ) + } else { + workspace.open_path_preview( + project_path.clone(), + None, + true, + false, + true, + window, + cx, + ) + } + } - Match::History { path, .. } => { - let worktree_id = path.project.worktree_id; - if workspace - .project() - .read(cx) - .worktree_for_id(worktree_id, cx) - .is_some() - { - split_or_open( + Match::History { path, .. } => { + let worktree_id = path.project.worktree_id; + if workspace + .project() + .read(cx) + .worktree_for_id(worktree_id, cx) + .is_some() + { + split_or_open( + workspace, + ProjectPath { + worktree_id, + path: Arc::clone(&path.project.path), + }, + window, + cx, + ) + } else { + match path.absolute.as_ref() { + Some(abs_path) => { + if secondary { + workspace.split_abs_path( + abs_path.to_path_buf(), + false, + window, + cx, + ) + } else { + workspace.open_abs_path( + abs_path.to_path_buf(), + OpenOptions { + visible: Some(OpenVisible::None), + ..Default::default() + }, + window, + cx, + ) + } + } + None => split_or_open( workspace, ProjectPath { worktree_id, @@ -1506,88 +1533,52 @@ impl PickerDelegate for FileFinderDelegate { }, window, cx, - ) - } else { - match path.absolute.as_ref() { - Some(abs_path) => { - if secondary { - workspace.split_abs_path( - abs_path.to_path_buf(), - false, - window, - cx, - ) - } else { - workspace.open_abs_path( - abs_path.to_path_buf(), - OpenOptions { - visible: Some(OpenVisible::None), - ..Default::default() - }, - window, - cx, - ) - } - } - None => split_or_open( - workspace, - ProjectPath { - worktree_id, - path: Arc::clone(&path.project.path), - }, - window, - cx, - ), - } + ), } } - Match::Search(m) => split_or_open( - workspace, - ProjectPath { - worktree_id: WorktreeId::from_usize(m.0.worktree_id), - path: m.0.path.clone(), - }, - window, - cx, - ), } - }); + Match::Search(m) => split_or_open( + workspace, + ProjectPath { + worktree_id: WorktreeId::from_usize(m.0.worktree_id), + path: m.0.path.clone(), + }, + window, + cx, + ), + } + }); - let row = self - .latest_search_query - .as_ref() - .and_then(|query| query.path_position.row) - .map(|row| row.saturating_sub(1)); - let col = self - .latest_search_query - .as_ref() - .and_then(|query| query.path_position.column) - .unwrap_or(0) - .saturating_sub(1); - let finder = self.file_finder.clone(); + let row = self + .latest_search_query + .as_ref() + .and_then(|query| query.path_position.row) + .map(|row| row.saturating_sub(1)); + let col = self + .latest_search_query + .as_ref() + .and_then(|query| query.path_position.column) + .unwrap_or(0) + .saturating_sub(1); + let finder = self.file_finder.clone(); - cx.spawn_in(window, async move |_, cx| { - let item = open_task.await.notify_async_err(cx)?; - if let Some(row) = row { - if let Some(active_editor) = item.downcast::<Editor>() { - active_editor - .downgrade() - .update_in(cx, |editor, window, cx| { - editor.go_to_singleton_buffer_point( - Point::new(row, col), - window, - cx, - ); - }) - .log_err(); - } - } - finder.update(cx, |_, cx| cx.emit(DismissEvent)).ok()?; + cx.spawn_in(window, async move |_, cx| { + let item = open_task.await.notify_async_err(cx)?; + if let Some(row) = row + && let Some(active_editor) = item.downcast::<Editor>() + { + active_editor + .downgrade() + .update_in(cx, |editor, window, cx| { + editor.go_to_singleton_buffer_point(Point::new(row, col), window, cx); + }) + .log_err(); + } + finder.update(cx, |_, cx| cx.emit(DismissEvent)).ok()?; - Some(()) - }) - .detach(); - } + Some(()) + }) + .detach(); } } diff --git a/crates/file_finder/src/file_finder_tests.rs b/crates/file_finder/src/file_finder_tests.rs index db259ccef8..8203d1b1fd 100644 --- a/crates/file_finder/src/file_finder_tests.rs +++ b/crates/file_finder/src/file_finder_tests.rs @@ -1614,7 +1614,7 @@ async fn test_select_current_open_file_when_no_history(cx: &mut gpui::TestAppCon let picker = open_file_picker(&workspace, cx); picker.update(cx, |finder, _| { - assert_match_selection(&finder, 0, "1_qw"); + assert_match_selection(finder, 0, "1_qw"); }); } @@ -2623,7 +2623,7 @@ async fn open_queried_buffer( workspace: &Entity<Workspace>, cx: &mut gpui::VisualTestContext, ) -> Vec<FoundPath> { - let picker = open_file_picker(&workspace, cx); + let picker = open_file_picker(workspace, cx); cx.simulate_input(input); let history_items = picker.update(cx, |finder, _| { diff --git a/crates/file_finder/src/open_path_prompt.rs b/crates/file_finder/src/open_path_prompt.rs index 68ba7a78b5..ffe3d42a27 100644 --- a/crates/file_finder/src/open_path_prompt.rs +++ b/crates/file_finder/src/open_path_prompt.rs @@ -75,16 +75,16 @@ impl OpenPathDelegate { .. } => { let mut i = selected_match_index; - if let Some(user_input) = user_input { - if !user_input.exists || !user_input.is_dir { - if i == 0 { - return Some(CandidateInfo { - path: user_input.file.clone(), - is_dir: false, - }); - } else { - i -= 1; - } + if let Some(user_input) = user_input + && (!user_input.exists || !user_input.is_dir) + { + if i == 0 { + return Some(CandidateInfo { + path: user_input.file.clone(), + is_dir: false, + }); + } else { + i -= 1; } } let id = self.string_matches.get(i)?.candidate_id; @@ -112,7 +112,7 @@ impl OpenPathDelegate { entries, .. } => user_input - .into_iter() + .iter() .filter(|user_input| !user_input.exists || !user_input.is_dir) .map(|user_input| user_input.file.string.clone()) .chain(self.string_matches.iter().filter_map(|string_match| { @@ -637,7 +637,7 @@ impl PickerDelegate for OpenPathDelegate { FileIcons::get_folder_icon(false, cx)? } else { let path = path::Path::new(&candidate.path.string); - FileIcons::get_icon(&path, cx)? + FileIcons::get_icon(path, cx)? }; Some(Icon::from_path(icon).color(Color::Muted)) }); @@ -728,7 +728,7 @@ impl PickerDelegate for OpenPathDelegate { .child(LabelLike::new().child(label_with_highlights)), ) } - DirectoryState::None { .. } => return None, + DirectoryState::None { .. } => None, } } diff --git a/crates/file_icons/src/file_icons.rs b/crates/file_icons/src/file_icons.rs index 82a8e05d85..42c00fb12d 100644 --- a/crates/file_icons/src/file_icons.rs +++ b/crates/file_icons/src/file_icons.rs @@ -72,7 +72,7 @@ impl FileIcons { return maybe_path; } } - return this.get_icon_for_type("default", cx); + this.get_icon_for_type("default", cx) } fn default_icon_theme(cx: &App) -> Option<Arc<IconTheme>> { diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index f0936d400a..5b093ac6a0 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -590,9 +590,9 @@ mod tests { assert_eq!( fs.files_with_contents(Path::new("")), [ - (Path::new("/bar/baz").into(), b"qux".into()), - (Path::new("/foo/a").into(), b"lorem".into()), - (Path::new("/foo/b").into(), b"ipsum".into()) + (Path::new(path!("/bar/baz")).into(), b"qux".into()), + (Path::new(path!("/foo/a")).into(), b"lorem".into()), + (Path::new(path!("/foo/b")).into(), b"ipsum".into()) ] ); } diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 22bfdbcd66..11177512c3 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -420,18 +420,19 @@ impl Fs for RealFs { async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> { #[cfg(windows)] - if let Ok(Some(metadata)) = self.metadata(path).await { - if metadata.is_symlink && metadata.is_dir { - self.remove_dir( - path, - RemoveOptions { - recursive: false, - ignore_if_not_exists: true, - }, - ) - .await?; - return Ok(()); - } + if let Ok(Some(metadata)) = self.metadata(path).await + && metadata.is_symlink + && metadata.is_dir + { + self.remove_dir( + path, + RemoveOptions { + recursive: false, + ignore_if_not_exists: true, + }, + ) + .await?; + return Ok(()); } match smol::fs::remove_file(path).await { @@ -467,11 +468,11 @@ impl Fs for RealFs { #[cfg(any(target_os = "linux", target_os = "freebsd"))] async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> { - if let Ok(Some(metadata)) = self.metadata(path).await { - if metadata.is_symlink { - // TODO: trash_file does not support trashing symlinks yet - https://github.com/bilelmoussaoui/ashpd/issues/255 - return self.remove_file(path, RemoveOptions::default()).await; - } + if let Ok(Some(metadata)) = self.metadata(path).await + && metadata.is_symlink + { + // TODO: trash_file does not support trashing symlinks yet - https://github.com/bilelmoussaoui/ashpd/issues/255 + return self.remove_file(path, RemoveOptions::default()).await; } let file = smol::fs::File::open(path).await?; match trash::trash_file(&file.as_fd()).await { @@ -624,13 +625,13 @@ impl Fs for RealFs { async fn is_file(&self, path: &Path) -> bool { smol::fs::metadata(path) .await - .map_or(false, |metadata| metadata.is_file()) + .is_ok_and(|metadata| metadata.is_file()) } async fn is_dir(&self, path: &Path) -> bool { smol::fs::metadata(path) .await - .map_or(false, |metadata| metadata.is_dir()) + .is_ok_and(|metadata| metadata.is_dir()) } async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> { @@ -766,24 +767,23 @@ impl Fs for RealFs { let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default(); let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone())); - if watcher.add(path).is_err() { - // If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created. - if let Some(parent) = path.parent() { - if let Err(e) = watcher.add(parent) { - log::warn!("Failed to watch: {e}"); - } - } + // If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created. + if watcher.add(path).is_err() + && let Some(parent) = path.parent() + && let Err(e) = watcher.add(parent) + { + log::warn!("Failed to watch: {e}"); } // Check if path is a symlink and follow the target parent - if let Some(mut target) = self.read_link(&path).await.ok() { + if let Some(mut target) = self.read_link(path).await.ok() { // Check if symlink target is relative path, if so make it absolute - if target.is_relative() { - if let Some(parent) = path.parent() { - target = parent.join(target); - if let Ok(canonical) = self.canonicalize(&target).await { - target = SanitizedPath::from(canonical).as_path().to_path_buf(); - } + if target.is_relative() + && let Some(parent) = path.parent() + { + target = parent.join(target); + if let Ok(canonical) = self.canonicalize(&target).await { + target = SanitizedPath::from(canonical).as_path().to_path_buf(); } } watcher.add(&target).ok(); @@ -1068,13 +1068,13 @@ impl FakeFsState { let current_entry = *entry_stack.last()?; if let FakeFsEntry::Dir { entries, .. } = current_entry { let entry = entries.get(name.to_str().unwrap())?; - if path_components.peek().is_some() || follow_symlink { - if let FakeFsEntry::Symlink { target, .. } = entry { - let mut target = target.clone(); - target.extend(path_components); - path = target; - continue 'outer; - } + if (path_components.peek().is_some() || follow_symlink) + && let FakeFsEntry::Symlink { target, .. } = entry + { + let mut target = target.clone(); + target.extend(path_components); + path = target; + continue 'outer; } entry_stack.push(entry); canonical_path = canonical_path.join(name); @@ -1101,7 +1101,9 @@ impl FakeFsState { ) -> Option<(&mut FakeFsEntry, PathBuf)> { let canonical_path = self.canonicalize(target, follow_symlink)?; - let mut components = canonical_path.components(); + let mut components = canonical_path + .components() + .skip_while(|component| matches!(component, Component::Prefix(_))); let Some(Component::RootDir) = components.next() else { panic!( "the path {:?} was not canonicalized properly {:?}", @@ -1566,10 +1568,10 @@ impl FakeFs { pub fn insert_branches(&self, dot_git: &Path, branches: &[&str]) { self.with_git_state(dot_git, true, |state| { - if let Some(first) = branches.first() { - if state.current_branch_name.is_none() { - state.current_branch_name = Some(first.to_string()) - } + if let Some(first) = branches.first() + && state.current_branch_name.is_none() + { + state.current_branch_name = Some(first.to_string()) } state .branches @@ -1677,7 +1679,7 @@ impl FakeFs { /// by mutating the head, index, and unmerged state. pub fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, FileStatus)]) { let workdir_path = dot_git.parent().unwrap(); - let workdir_contents = self.files_with_contents(&workdir_path); + let workdir_contents = self.files_with_contents(workdir_path); self.with_git_state(dot_git, true, |state| { state.index_contents.clear(); state.head_contents.clear(); @@ -2244,7 +2246,7 @@ impl Fs for FakeFs { async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>> { self.simulate_random_delay().await; let mut state = self.state.lock(); - let inode = match state.entry(&path)? { + let inode = match state.entry(path)? { FakeFsEntry::File { inode, .. } => *inode, FakeFsEntry::Dir { inode, .. } => *inode, _ => unreachable!(), @@ -2417,12 +2419,11 @@ impl Fs for FakeFs { let watcher = watcher.clone(); move |events| { let result = events.iter().any(|evt_path| { - let result = watcher + watcher .prefixes .lock() .iter() - .any(|prefix| evt_path.path.starts_with(prefix)); - result + .any(|prefix| evt_path.path.starts_with(prefix)) }); let executor = executor.clone(); async move { diff --git a/crates/fs/src/mac_watcher.rs b/crates/fs/src/mac_watcher.rs index aa75ad31d9..7bd176639f 100644 --- a/crates/fs/src/mac_watcher.rs +++ b/crates/fs/src/mac_watcher.rs @@ -41,10 +41,9 @@ impl Watcher for MacWatcher { if let Some((watched_path, _)) = handles .range::<Path, _>((Bound::Unbounded, Bound::Included(path))) .next_back() + && path.starts_with(watched_path) { - if path.starts_with(watched_path) { - return Ok(()); - } + return Ok(()); } let (stream, handle) = EventStream::new(&[path], self.latency); diff --git a/crates/fsevent/src/fsevent.rs b/crates/fsevent/src/fsevent.rs index 81ca0a4114..c97ab5f35d 100644 --- a/crates/fsevent/src/fsevent.rs +++ b/crates/fsevent/src/fsevent.rs @@ -178,40 +178,39 @@ impl EventStream { flags.contains(StreamFlags::USER_DROPPED) || flags.contains(StreamFlags::KERNEL_DROPPED) }) + && let Some(last_valid_event_id) = state.last_valid_event_id.take() { - if let Some(last_valid_event_id) = state.last_valid_event_id.take() { - fs::FSEventStreamStop(state.stream); - fs::FSEventStreamInvalidate(state.stream); - fs::FSEventStreamRelease(state.stream); + fs::FSEventStreamStop(state.stream); + fs::FSEventStreamInvalidate(state.stream); + fs::FSEventStreamRelease(state.stream); - let stream_context = fs::FSEventStreamContext { - version: 0, - info, - retain: None, - release: None, - copy_description: None, - }; - let stream = fs::FSEventStreamCreate( - cf::kCFAllocatorDefault, - Self::trampoline, - &stream_context, - state.paths, - last_valid_event_id, - state.latency.as_secs_f64(), - fs::kFSEventStreamCreateFlagFileEvents - | fs::kFSEventStreamCreateFlagNoDefer - | fs::kFSEventStreamCreateFlagWatchRoot, - ); + let stream_context = fs::FSEventStreamContext { + version: 0, + info, + retain: None, + release: None, + copy_description: None, + }; + let stream = fs::FSEventStreamCreate( + cf::kCFAllocatorDefault, + Self::trampoline, + &stream_context, + state.paths, + last_valid_event_id, + state.latency.as_secs_f64(), + fs::kFSEventStreamCreateFlagFileEvents + | fs::kFSEventStreamCreateFlagNoDefer + | fs::kFSEventStreamCreateFlagWatchRoot, + ); - state.stream = stream; - fs::FSEventStreamScheduleWithRunLoop( - state.stream, - cf::CFRunLoopGetCurrent(), - cf::kCFRunLoopDefaultMode, - ); - fs::FSEventStreamStart(state.stream); - stream_restarted = true; - } + state.stream = stream; + fs::FSEventStreamScheduleWithRunLoop( + state.stream, + cf::CFRunLoopGetCurrent(), + cf::kCFRunLoopDefaultMode, + ); + fs::FSEventStreamStart(state.stream); + stream_restarted = true; } if !stream_restarted { diff --git a/crates/git/src/blame.rs b/crates/git/src/blame.rs index 6f12681ea0..24b2c44218 100644 --- a/crates/git/src/blame.rs +++ b/crates/git/src/blame.rs @@ -289,14 +289,12 @@ fn parse_git_blame(output: &str) -> Result<Vec<BlameEntry>> { } }; - if done { - if let Some(entry) = current_entry.take() { - index.insert(entry.sha, entries.len()); + if done && let Some(entry) = current_entry.take() { + index.insert(entry.sha, entries.len()); - // We only want annotations that have a commit. - if !entry.sha.is_zero() { - entries.push(entry); - } + // We only want annotations that have a commit. + if !entry.sha.is_zero() { + entries.push(entry); } } } diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 49eee84840..9c125d2c47 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -269,10 +269,8 @@ impl GitExcludeOverride { pub async fn restore_original(&mut self) -> Result<()> { if let Some(ref original) = self.original_excludes { smol::fs::write(&self.git_exclude_path, original).await?; - } else { - if self.git_exclude_path.exists() { - smol::fs::remove_file(&self.git_exclude_path).await?; - } + } else if self.git_exclude_path.exists() { + smol::fs::remove_file(&self.git_exclude_path).await?; } self.added_excludes = None; @@ -858,7 +856,7 @@ impl GitRepository for RealGitRepository { let output = new_smol_command(&git_binary_path) .current_dir(&working_directory) .envs(env.iter()) - .args(["update-index", "--add", "--cacheinfo", "100644", &sha]) + .args(["update-index", "--add", "--cacheinfo", "100644", sha]) .arg(path.to_unix_style()) .output() .await?; @@ -959,7 +957,7 @@ impl GitRepository for RealGitRepository { Ok(working_directory) => working_directory, Err(e) => return Task::ready(Err(e)), }; - let args = git_status_args(&path_prefixes); + let args = git_status_args(path_prefixes); log::debug!("Checking for git status in {path_prefixes:?}"); self.executor.spawn(async move { let output = new_std_command(&git_binary_path) @@ -1056,7 +1054,7 @@ impl GitRepository for RealGitRepository { let (_, branch_name) = name.split_once("/").context("Unexpected branch format")?; let revision = revision.get(); let branch_commit = revision.peel_to_commit()?; - let mut branch = repo.branch(&branch_name, &branch_commit, false)?; + let mut branch = repo.branch(branch_name, &branch_commit, false)?; branch.set_upstream(Some(&name))?; branch } else { @@ -1447,12 +1445,11 @@ impl GitRepository for RealGitRepository { let mut remote_branches = vec![]; let mut add_if_matching = async |remote_head: &str| { - if let Ok(merge_base) = git_cmd(&["merge-base", &head, remote_head]).await { - if merge_base.trim() == head { - if let Some(s) = remote_head.strip_prefix("refs/remotes/") { - remote_branches.push(s.to_owned().into()); - } - } + if let Ok(merge_base) = git_cmd(&["merge-base", &head, remote_head]).await + && merge_base.trim() == head + && let Some(s) = remote_head.strip_prefix("refs/remotes/") + { + remote_branches.push(s.to_owned().into()); } }; @@ -1574,10 +1571,9 @@ impl GitRepository for RealGitRepository { Err(error) => { if let Some(GitBinaryCommandError { status, .. }) = error.downcast_ref::<GitBinaryCommandError>() + && status.code() == Some(1) { - if status.code() == Some(1) { - return Ok(false); - } + return Ok(false); } Err(error) @@ -2032,7 +2028,7 @@ fn parse_branch_input(input: &str) -> Result<Vec<Branch>> { branches.push(Branch { is_head: is_current_branch, - ref_name: ref_name, + ref_name, most_recent_commit: Some(CommitSummary { sha: head_sha, subject, @@ -2054,7 +2050,7 @@ fn parse_branch_input(input: &str) -> Result<Vec<Branch>> { } fn parse_upstream_track(upstream_track: &str) -> Result<UpstreamTracking> { - if upstream_track == "" { + if upstream_track.is_empty() { return Ok(UpstreamTracking::Tracked(UpstreamTrackingStatus { ahead: 0, behind: 0, @@ -2349,7 +2345,7 @@ mod tests { #[allow(clippy::octal_escapes)] let input = "*\0060964da10574cd9bf06463a53bf6e0769c5c45e\0\0refs/heads/zed-patches\0refs/remotes/origin/zed-patches\0\01733187470\0generated protobuf\n"; assert_eq!( - parse_branch_input(&input).unwrap(), + parse_branch_input(input).unwrap(), vec![Branch { is_head: true, ref_name: "refs/heads/zed-patches".into(), diff --git a/crates/git/src/status.rs b/crates/git/src/status.rs index 6158b51798..92836042f2 100644 --- a/crates/git/src/status.rs +++ b/crates/git/src/status.rs @@ -468,7 +468,7 @@ impl FromStr for GitStatus { Some((path, status)) }) .collect::<Vec<_>>(); - entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b)); + entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); // When a file exists in HEAD, is deleted in the index, and exists again in the working copy, // git produces two lines for it, one reading `D ` (deleted in index, unmodified in working copy) // and the other reading `??` (untracked). Merge these two into the equivalent of `DA`. diff --git a/crates/git_hosting_providers/src/git_hosting_providers.rs b/crates/git_hosting_providers/src/git_hosting_providers.rs index b31412ed4a..1d88c47f2e 100644 --- a/crates/git_hosting_providers/src/git_hosting_providers.rs +++ b/crates/git_hosting_providers/src/git_hosting_providers.rs @@ -49,13 +49,13 @@ pub fn register_additional_providers( pub fn get_host_from_git_remote_url(remote_url: &str) -> Result<String> { maybe!({ - if let Some(remote_url) = remote_url.strip_prefix("git@") { - if let Some((host, _)) = remote_url.trim_start_matches("git@").split_once(':') { - return Some(host.to_string()); - } + if let Some(remote_url) = remote_url.strip_prefix("git@") + && let Some((host, _)) = remote_url.trim_start_matches("git@").split_once(':') + { + return Some(host.to_string()); } - Url::parse(&remote_url) + Url::parse(remote_url) .ok() .and_then(|remote_url| remote_url.host_str().map(|host| host.to_string())) }) diff --git a/crates/git_hosting_providers/src/providers/chromium.rs b/crates/git_hosting_providers/src/providers/chromium.rs index b68c629ec7..5d940fb496 100644 --- a/crates/git_hosting_providers/src/providers/chromium.rs +++ b/crates/git_hosting_providers/src/providers/chromium.rs @@ -292,7 +292,7 @@ mod tests { assert_eq!( Chromium - .extract_pull_request(&remote, &message) + .extract_pull_request(&remote, message) .unwrap() .url .as_str(), diff --git a/crates/git_hosting_providers/src/providers/github.rs b/crates/git_hosting_providers/src/providers/github.rs index 30f8d058a7..4475afeb49 100644 --- a/crates/git_hosting_providers/src/providers/github.rs +++ b/crates/git_hosting_providers/src/providers/github.rs @@ -474,7 +474,7 @@ mod tests { assert_eq!( github - .extract_pull_request(&remote, &message) + .extract_pull_request(&remote, message) .unwrap() .url .as_str(), @@ -488,6 +488,6 @@ mod tests { See the original PR, this is a fix. "# }; - assert_eq!(github.extract_pull_request(&remote, &message), None); + assert_eq!(github.extract_pull_request(&remote, message), None); } } diff --git a/crates/git_ui/src/commit_modal.rs b/crates/git_ui/src/commit_modal.rs index 5e7430ebc6..4303f53275 100644 --- a/crates/git_ui/src/commit_modal.rs +++ b/crates/git_ui/src/commit_modal.rs @@ -135,11 +135,10 @@ impl CommitModal { .as_ref() .and_then(|repo| repo.read(cx).head_commit.as_ref()) .is_some() + && !git_panel.amend_pending() { - if !git_panel.amend_pending() { - git_panel.set_amend_pending(true, cx); - git_panel.load_last_commit_message_if_empty(cx); - } + git_panel.set_amend_pending(true, cx); + git_panel.load_last_commit_message_if_empty(cx); } } ForceMode::Commit => { @@ -195,12 +194,12 @@ impl CommitModal { let commit_message = commit_editor.read(cx).text(cx); - if let Some(suggested_commit_message) = suggested_commit_message { - if commit_message.is_empty() { - commit_editor.update(cx, |editor, cx| { - editor.set_placeholder_text(suggested_commit_message, cx); - }); - } + if let Some(suggested_commit_message) = suggested_commit_message + && commit_message.is_empty() + { + commit_editor.update(cx, |editor, cx| { + editor.set_placeholder_text(suggested_commit_message, cx); + }); } let focus_handle = commit_editor.focus_handle(cx); diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index c8c237fe90..d428ccbb05 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -88,11 +88,10 @@ impl CommitView { let ix = pane.items().position(|item| { let commit_view = item.downcast::<CommitView>(); commit_view - .map_or(false, |view| view.read(cx).commit.sha == commit.sha) + .is_some_and(|view| view.read(cx).commit.sha == commit.sha) }); if let Some(ix) = ix { pane.activate_item(ix, true, true, window, cx); - return; } else { pane.add_item(Box::new(commit_view), true, true, None, window, cx); } @@ -160,7 +159,7 @@ impl CommitView { }); } - cx.spawn(async move |this, mut cx| { + cx.spawn(async move |this, cx| { for file in commit_diff.files { let is_deleted = file.new_text.is_none(); let new_text = file.new_text.unwrap_or_default(); @@ -179,9 +178,9 @@ impl CommitView { worktree_id, }) as Arc<dyn language::File>; - let buffer = build_buffer(new_text, file, &language_registry, &mut cx).await?; + let buffer = build_buffer(new_text, file, &language_registry, cx).await?; let buffer_diff = - build_buffer_diff(old_text, &buffer, &language_registry, &mut cx).await?; + build_buffer_diff(old_text, &buffer, &language_registry, cx).await?; this.update(cx, |this, cx| { this.multibuffer.update(cx, |multibuffer, cx| { diff --git a/crates/git_ui/src/conflict_view.rs b/crates/git_ui/src/conflict_view.rs index 6482ebb9f8..5c1b1325a5 100644 --- a/crates/git_ui/src/conflict_view.rs +++ b/crates/git_ui/src/conflict_view.rs @@ -156,7 +156,7 @@ fn buffers_removed(editor: &mut Editor, removed_buffer_ids: &[BufferId], cx: &mu .unwrap() .buffers .retain(|buffer_id, buffer| { - if removed_buffer_ids.contains(&buffer_id) { + if removed_buffer_ids.contains(buffer_id) { removed_block_ids.extend(buffer.block_ids.iter().map(|(_, block_id)| *block_id)); false } else { @@ -222,12 +222,12 @@ fn conflicts_updated( let precedes_start = range .context .start - .cmp(&conflict_range.start, &buffer_snapshot) + .cmp(&conflict_range.start, buffer_snapshot) .is_le(); let follows_end = range .context .end - .cmp(&conflict_range.start, &buffer_snapshot) + .cmp(&conflict_range.start, buffer_snapshot) .is_ge(); precedes_start && follows_end }) else { @@ -268,12 +268,12 @@ fn conflicts_updated( let precedes_start = range .context .start - .cmp(&conflict.range.start, &buffer_snapshot) + .cmp(&conflict.range.start, buffer_snapshot) .is_le(); let follows_end = range .context .end - .cmp(&conflict.range.start, &buffer_snapshot) + .cmp(&conflict.range.start, buffer_snapshot) .is_ge(); precedes_start && follows_end }) else { diff --git a/crates/git_ui/src/file_diff_view.rs b/crates/git_ui/src/file_diff_view.rs index 2f8a744ed8..a320888b3b 100644 --- a/crates/git_ui/src/file_diff_view.rs +++ b/crates/git_ui/src/file_diff_view.rs @@ -123,7 +123,7 @@ impl FileDiffView { old_buffer, new_buffer, _recalculate_diff_task: cx.spawn(async move |this, cx| { - while let Ok(_) = buffer_changes_rx.recv().await { + while buffer_changes_rx.recv().await.is_ok() { loop { let mut timer = cx .background_executor() @@ -398,7 +398,7 @@ mod tests { let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await; - let (workspace, mut cx) = + let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); let diff_view = workspace @@ -417,7 +417,7 @@ mod tests { // Verify initial diff assert_state_with_diff( &diff_view.read_with(cx, |diff_view, _| diff_view.editor.clone()), - &mut cx, + cx, &unindent( " - old line 1 @@ -452,7 +452,7 @@ mod tests { cx.executor().advance_clock(RECALCULATE_DIFF_DEBOUNCE); assert_state_with_diff( &diff_view.read_with(cx, |diff_view, _| diff_view.editor.clone()), - &mut cx, + cx, &unindent( " - old line 1 @@ -487,7 +487,7 @@ mod tests { cx.executor().advance_clock(RECALCULATE_DIFF_DEBOUNCE); assert_state_with_diff( &diff_view.read_with(cx, |diff_view, _| diff_view.editor.clone()), - &mut cx, + cx, &unindent( " ˇnew line 1 diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 208d42c8dc..9f6bfb06af 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -101,7 +101,7 @@ fn prompt<T>( where T: IntoEnumIterator + VariantNames + 'static, { - let rx = window.prompt(PromptLevel::Info, msg, detail, &T::VARIANTS, cx); + let rx = window.prompt(PromptLevel::Info, msg, detail, T::VARIANTS, cx); cx.spawn(async move |_| Ok(T::iter().nth(rx.await?).unwrap())) } @@ -367,7 +367,7 @@ impl GitPanel { let git_store = project.read(cx).git_store().clone(); let active_repository = project.read(cx).active_repository(cx); - let git_panel = cx.new(|cx| { + cx.new(|cx| { let focus_handle = cx.focus_handle(); cx.on_focus(&focus_handle, window, Self::focus_in).detach(); @@ -480,23 +480,21 @@ impl GitPanel { this.schedule_update(false, window, cx); this - }); - - git_panel + }) } pub fn entry_by_path(&self, path: &RepoPath, cx: &App) -> Option<usize> { if GitPanelSettings::get_global(cx).sort_by_path { return self .entries - .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(&path)) + .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(path)) .ok(); } if self.conflicted_count > 0 { let conflicted_start = 1; if let Ok(ix) = self.entries[conflicted_start..conflicted_start + self.conflicted_count] - .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(&path)) + .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(path)) { return Some(conflicted_start + ix); } @@ -508,7 +506,7 @@ impl GitPanel { 0 } + 1; if let Ok(ix) = self.entries[tracked_start..tracked_start + self.tracked_count] - .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(&path)) + .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(path)) { return Some(tracked_start + ix); } @@ -524,7 +522,7 @@ impl GitPanel { 0 } + 1; if let Ok(ix) = self.entries[untracked_start..untracked_start + self.new_count] - .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(&path)) + .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(path)) { return Some(untracked_start + ix); } @@ -612,7 +610,7 @@ impl GitPanel { if window .focused(cx) - .map_or(false, |focused| self.focus_handle == focused) + .is_some_and(|focused| self.focus_handle == focused) { dispatch_context.add("menu"); dispatch_context.add("ChangesList"); @@ -731,9 +729,7 @@ impl GitPanel { let have_entries = self .active_repository .as_ref() - .map_or(false, |active_repository| { - active_repository.read(cx).status_summary().count > 0 - }); + .is_some_and(|active_repository| active_repository.read(cx).status_summary().count > 0); if have_entries && self.selected_entry.is_none() { self.selected_entry = Some(1); self.scroll_to_selected_entry(cx); @@ -763,19 +759,17 @@ impl GitPanel { let workspace = self.workspace.upgrade()?; let git_repo = self.active_repository.as_ref()?; - if let Some(project_diff) = workspace.read(cx).active_item_as::<ProjectDiff>(cx) { - if let Some(project_path) = project_diff.read(cx).active_path(cx) { - if Some(&entry.repo_path) - == git_repo - .read(cx) - .project_path_to_repo_path(&project_path, cx) - .as_ref() - { - project_diff.focus_handle(cx).focus(window); - project_diff.update(cx, |project_diff, cx| project_diff.autoscroll(cx)); - return None; - } - } + if let Some(project_diff) = workspace.read(cx).active_item_as::<ProjectDiff>(cx) + && let Some(project_path) = project_diff.read(cx).active_path(cx) + && Some(&entry.repo_path) + == git_repo + .read(cx) + .project_path_to_repo_path(&project_path, cx) + .as_ref() + { + project_diff.focus_handle(cx).focus(window); + project_diff.update(cx, |project_diff, cx| project_diff.autoscroll(cx)); + return None; }; self.workspace @@ -1039,16 +1033,13 @@ impl GitPanel { window, cx, ); - cx.spawn(async move |this, cx| match prompt.await { - Ok(RestoreCancel::RestoreTrackedFiles) => { + cx.spawn(async move |this, cx| { + if let Ok(RestoreCancel::RestoreTrackedFiles) = prompt.await { this.update(cx, |this, cx| { this.perform_checkout(entries, cx); }) .ok(); } - _ => { - return; - } }) .detach(); } @@ -1178,7 +1169,7 @@ impl GitPanel { .iter() .filter_map(|entry| entry.status_entry()) .filter(|status_entry| { - section.contains(&status_entry, repository) + section.contains(status_entry, repository) && status_entry.staging.as_bool() != Some(goal_staged_state) }) .map(|status_entry| status_entry.clone()) @@ -1479,13 +1470,12 @@ impl GitPanel { fn has_commit_message(&self, cx: &mut Context<Self>) -> bool { let text = self.commit_editor.read(cx).text(cx); if !text.trim().is_empty() { - return true; + true } else if text.is_empty() { - return self - .suggest_commit_message(cx) - .is_some_and(|text| !text.trim().is_empty()); + self.suggest_commit_message(cx) + .is_some_and(|text| !text.trim().is_empty()) } else { - return false; + false } } @@ -1789,7 +1779,7 @@ impl GitPanel { thinking_allowed: false, }; - let stream = model.stream_completion_text(request, &cx); + let stream = model.stream_completion_text(request, cx); match stream.await { Ok(mut messages) => { if !text_empty { @@ -1925,6 +1915,7 @@ impl GitPanel { files: false, directories: true, multiple: false, + prompt: Some("Select as Repository Destination".into()), }); let workspace = self.workspace.clone(); @@ -2350,10 +2341,11 @@ impl GitPanel { new_co_authors.push((name.clone(), email.clone())) } } - if !project.is_local() && !project.is_read_only(cx) { - if let Some(local_committer) = self.local_committer(room, cx) { - new_co_authors.push(local_committer); - } + if !project.is_local() + && !project.is_read_only(cx) + && let Some(local_committer) = self.local_committer(room, cx) + { + new_co_authors.push(local_committer); } new_co_authors } @@ -2591,16 +2583,15 @@ impl GitPanel { for pending in self.pending.iter() { if pending.target_status == TargetStatus::Staged { pending_staged_count += pending.entries.len(); - last_pending_staged = pending.entries.iter().next().cloned(); + last_pending_staged = pending.entries.first().cloned(); } - if let Some(single_staged) = &single_staged_entry { - if pending + if let Some(single_staged) = &single_staged_entry + && pending .entries .iter() .any(|entry| entry.repo_path == single_staged.repo_path) - { - pending_status_for_single_staged = Some(pending.target_status); - } + { + pending_status_for_single_staged = Some(pending.target_status); } } @@ -2775,8 +2766,7 @@ impl GitPanel { .matches(git::repository::REMOTE_CANCELLED_BY_USER) .next() .is_some() - { - return; // Hide the cancelled by user message + { // Hide the cancelled by user message } else { workspace.update(cx, |workspace, cx| { let workspace_weak = cx.weak_entity(); @@ -3109,12 +3099,10 @@ impl GitPanel { } else { "Amend Tracked" } + } else if self.has_staged_changes() { + "Commit" } else { - if self.has_staged_changes() { - "Commit" - } else { - "Commit Tracked" - } + "Commit Tracked" } } @@ -4147,7 +4135,7 @@ impl Render for GitPanel { let has_write_access = self.has_write_access(cx); - let has_co_authors = room.map_or(false, |room| { + let has_co_authors = room.is_some_and(|room| { self.load_local_committer(cx); let room = room.read(cx); room.remote_participants() @@ -4258,7 +4246,7 @@ impl editor::Addon for GitPanelAddon { git_panel .read(cx) - .render_buffer_header_controls(&git_panel, &file, window, cx) + .render_buffer_header_controls(&git_panel, file, window, cx) } } @@ -4454,12 +4442,10 @@ impl RenderOnce for PanelRepoFooter { // ideally, show the whole branch and repo names but // when we can't, use a budget to allocate space between the two - let (repo_display_len, branch_display_len) = if branch_actual_len + repo_actual_len - <= LABEL_CHARACTER_BUDGET - { - (repo_actual_len, branch_actual_len) - } else { - if branch_actual_len <= MAX_BRANCH_LEN { + let (repo_display_len, branch_display_len) = + if branch_actual_len + repo_actual_len <= LABEL_CHARACTER_BUDGET { + (repo_actual_len, branch_actual_len) + } else if branch_actual_len <= MAX_BRANCH_LEN { let repo_space = (LABEL_CHARACTER_BUDGET - branch_actual_len).min(MAX_REPO_LEN); (repo_space, branch_actual_len) } else if repo_actual_len <= MAX_REPO_LEN { @@ -4467,8 +4453,7 @@ impl RenderOnce for PanelRepoFooter { (repo_actual_len, branch_space) } else { (MAX_REPO_LEN, MAX_BRANCH_LEN) - } - }; + }; let truncated_repo_name = if repo_actual_len <= repo_display_len { active_repo_name.to_string() @@ -4899,7 +4884,7 @@ mod tests { project .read(cx) .worktrees(cx) - .nth(0) + .next() .unwrap() .read(cx) .as_local() @@ -5024,7 +5009,7 @@ mod tests { project .read(cx) .worktrees(cx) - .nth(0) + .next() .unwrap() .read(cx) .as_local() @@ -5075,7 +5060,7 @@ mod tests { project .read(cx) .worktrees(cx) - .nth(0) + .next() .unwrap() .read(cx) .as_local() @@ -5124,7 +5109,7 @@ mod tests { project .read(cx) .worktrees(cx) - .nth(0) + .next() .unwrap() .read(cx) .as_local() diff --git a/crates/git_ui/src/picker_prompt.rs b/crates/git_ui/src/picker_prompt.rs index 4077e0f362..3f1d507c42 100644 --- a/crates/git_ui/src/picker_prompt.rs +++ b/crates/git_ui/src/picker_prompt.rs @@ -152,7 +152,7 @@ impl PickerDelegate for PickerPromptDelegate { .all_options .iter() .enumerate() - .map(|(ix, option)| StringMatchCandidate::new(ix, &option)) + .map(|(ix, option)| StringMatchCandidate::new(ix, option)) .collect::<Vec<StringMatchCandidate>>() }); let Some(candidates) = candidates.log_err() else { diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index d6a4e27286..cc1535b7c3 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -280,7 +280,7 @@ impl ProjectDiff { fn button_states(&self, cx: &App) -> ButtonStates { let editor = self.editor.read(cx); let snapshot = self.multibuffer.read(cx).snapshot(cx); - let prev_next = snapshot.diff_hunks().skip(1).next().is_some(); + let prev_next = snapshot.diff_hunks().nth(1).is_some(); let mut selection = true; let mut ranges = editor @@ -329,14 +329,14 @@ impl ProjectDiff { }) .ok(); - return ButtonStates { + ButtonStates { stage: has_unstaged_hunks, unstage: has_staged_hunks, prev_next, selection, stage_all, unstage_all, - }; + } } fn handle_editor_event( @@ -346,27 +346,24 @@ impl ProjectDiff { window: &mut Window, cx: &mut Context<Self>, ) { - match event { - EditorEvent::SelectionsChanged { local: true } => { - let Some(project_path) = self.active_path(cx) else { - return; - }; - self.workspace - .update(cx, |workspace, cx| { - if let Some(git_panel) = workspace.panel::<GitPanel>(cx) { - git_panel.update(cx, |git_panel, cx| { - git_panel.select_entry_by_path(project_path, window, cx) - }) - } - }) - .ok(); - } - _ => {} + if let EditorEvent::SelectionsChanged { local: true } = event { + let Some(project_path) = self.active_path(cx) else { + return; + }; + self.workspace + .update(cx, |workspace, cx| { + if let Some(git_panel) = workspace.panel::<GitPanel>(cx) { + git_panel.update(cx, |git_panel, cx| { + git_panel.select_entry_by_path(project_path, window, cx) + }) + } + }) + .ok(); } - if editor.focus_handle(cx).contains_focused(window, cx) { - if self.multibuffer.read(cx).is_empty() { - self.focus_handle.focus(window) - } + if editor.focus_handle(cx).contains_focused(window, cx) + && self.multibuffer.read(cx).is_empty() + { + self.focus_handle.focus(window) } } @@ -513,7 +510,7 @@ impl ProjectDiff { mut recv: postage::watch::Receiver<()>, cx: &mut AsyncWindowContext, ) -> Result<()> { - while let Some(_) = recv.next().await { + while (recv.next().await).is_some() { let buffers_to_load = this.update(cx, |this, cx| this.load_buffers(cx))?; for buffer_to_load in buffers_to_load { if let Some(buffer) = buffer_to_load.await.log_err() { @@ -1173,7 +1170,7 @@ impl RenderOnce for ProjectDiffEmptyState { .child(Label::new("No Changes").color(Color::Muted)) } else { this.when_some(self.current_branch.as_ref(), |this, branch| { - this.child(has_branch_container(&branch)) + this.child(has_branch_container(branch)) }) } }), @@ -1332,14 +1329,14 @@ fn merge_anchor_ranges<'a>( loop { if let Some(left_range) = left .peek() - .filter(|range| range.start.cmp(&next_range.end, &snapshot).is_le()) + .filter(|range| range.start.cmp(&next_range.end, snapshot).is_le()) .cloned() { left.next(); next_range.end = left_range.end; } else if let Some(right_range) = right .peek() - .filter(|range| range.start.cmp(&next_range.end, &snapshot).is_le()) + .filter(|range| range.start.cmp(&next_range.end, snapshot).is_le()) .cloned() { right.next(); diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs index 005c1e18b4..e38e3698d5 100644 --- a/crates/git_ui/src/text_diff_view.rs +++ b/crates/git_ui/src/text_diff_view.rs @@ -207,7 +207,7 @@ impl TextDiffView { path: Some(format!("Clipboard ↔ {selection_location_path}").into()), buffer_changes_tx, _recalculate_diff_task: cx.spawn(async move |_, cx| { - while let Ok(_) = buffer_changes_rx.recv().await { + while buffer_changes_rx.recv().await.is_ok() { loop { let mut timer = cx .background_executor() @@ -686,7 +686,7 @@ mod tests { let project = Project::test(fs, [project_root.as_ref()], cx).await; - let (workspace, mut cx) = + let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); let buffer = project @@ -725,7 +725,7 @@ mod tests { assert_state_with_diff( &diff_view.read_with(cx, |diff_view, _| diff_view.diff_editor.clone()), - &mut cx, + cx, expected_diff, ); diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index af92621378..23729be062 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -95,10 +95,8 @@ impl CursorPosition { .ok() .unwrap_or(true); - if !is_singleton { - if let Some(debounce) = debounce { - cx.background_executor().timer(debounce).await; - } + if !is_singleton && let Some(debounce) = debounce { + cx.background_executor().timer(debounce).await; } editor @@ -131,7 +129,7 @@ impl CursorPosition { cursor_position.selected_count.lines += 1; } } - if last_selection.as_ref().map_or(true, |last_selection| { + if last_selection.as_ref().is_none_or(|last_selection| { selection.id > last_selection.id }) { last_selection = Some(selection); @@ -234,13 +232,11 @@ impl Render for CursorPosition { if let Some(editor) = workspace .active_item(cx) .and_then(|item| item.act_as::<Editor>(cx)) + && let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx) { - if let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx) - { - workspace.toggle_modal(window, cx, |window, cx| { - crate::GoToLine::new(editor, buffer, window, cx) - }) - } + workspace.toggle_modal(window, cx, |window, cx| { + crate::GoToLine::new(editor, buffer, window, cx) + }) } }); } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 1ac933e316..908e61cac7 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -103,11 +103,11 @@ impl GoToLine { return; }; editor.update(cx, |editor, cx| { - if let Some(placeholder_text) = editor.placeholder_text() { - if editor.text(cx).is_empty() { - let placeholder_text = placeholder_text.to_string(); - editor.set_text(placeholder_text, window, cx); - } + if let Some(placeholder_text) = editor.placeholder_text() + && editor.text(cx).is_empty() + { + let placeholder_text = placeholder_text.to_string(); + editor.set_text(placeholder_text, window, cx); } }); } diff --git a/crates/google_ai/src/google_ai.rs b/crates/google_ai/src/google_ai.rs index dfa51d024c..ca0aa309b1 100644 --- a/crates/google_ai/src/google_ai.rs +++ b/crates/google_ai/src/google_ai.rs @@ -106,10 +106,9 @@ pub fn validate_generate_content_request(request: &GenerateContentRequest) -> Re .contents .iter() .find(|content| content.role == Role::User) + && user_content.parts.is_empty() { - if user_content.parts.is_empty() { - bail!("User content must contain at least one part"); - } + bail!("User content must contain at least one part"); } Ok(()) @@ -267,7 +266,7 @@ pub struct CitationMetadata { pub struct PromptFeedback { #[serde(skip_serializing_if = "Option::is_none")] pub block_reason: Option<String>, - pub safety_ratings: Vec<SafetyRating>, + pub safety_ratings: Option<Vec<SafetyRating>>, #[serde(skip_serializing_if = "Option::is_none")] pub block_reason_message: Option<String>, } @@ -478,10 +477,10 @@ impl<'de> Deserialize<'de> for ModelName { model_id: id.to_string(), }) } else { - return Err(serde::de::Error::custom(format!( + Err(serde::de::Error::custom(format!( "Expected model name to begin with {}, got: {}", MODEL_NAME_PREFIX, string - ))); + ))) } } } diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 6be8c5fd1f..9f5b66087d 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -119,6 +119,7 @@ serde_json.workspace = true slotmap = "1.0.6" smallvec.workspace = true smol.workspace = true +stacksafe.workspace = true strum.workspace = true sum_tree.workspace = true taffy = "=0.9.0" diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 93a1c15c41..0040046f90 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -327,10 +327,10 @@ mod windows { /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler. fn find_fxc_compiler() -> String { // Check environment variable - if let Ok(path) = std::env::var("GPUI_FXC_PATH") { - if Path::new(&path).exists() { - return path; - } + if let Ok(path) = std::env::var("GPUI_FXC_PATH") + && Path::new(&path).exists() + { + return path; } // Try to find in PATH @@ -338,11 +338,10 @@ mod windows { if let Ok(output) = std::process::Command::new("where.exe") .arg("fxc.exe") .output() + && output.status.success() { - if output.status.success() { - let path = String::from_utf8_lossy(&output.stdout); - return path.trim().to_string(); - } + let path = String::from_utf8_lossy(&output.stdout); + return path.trim().to_string(); } // Check the default path @@ -374,7 +373,7 @@ mod windows { shader_path, "vs_4_1", ); - generate_rust_binding(&const_name, &output_file, &rust_binding_path); + generate_rust_binding(&const_name, &output_file, rust_binding_path); // Compile fragment shader let output_file = format!("{}/{}_ps.h", out_dir, module); @@ -387,7 +386,7 @@ mod windows { shader_path, "ps_4_1", ); - generate_rust_binding(&const_name, &output_file, &rust_binding_path); + generate_rust_binding(&const_name, &output_file, rust_binding_path); } fn compile_shader_impl( diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index b0f560e38d..ae635c94b8 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -137,14 +137,14 @@ impl TextInput { fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) { if !self.selected_range.is_empty() { cx.write_to_clipboard(ClipboardItem::new_string( - (&self.content[self.selected_range.clone()]).to_string(), + self.content[self.selected_range.clone()].to_string(), )); } } fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) { if !self.selected_range.is_empty() { cx.write_to_clipboard(ClipboardItem::new_string( - (&self.content[self.selected_range.clone()]).to_string(), + self.content[self.selected_range.clone()].to_string(), )); self.replace_text_in_range(None, "", window, cx) } @@ -549,10 +549,10 @@ impl Element for TextElement { line.paint(bounds.origin, window.line_height(), window, cx) .unwrap(); - if focus_handle.is_focused(window) { - if let Some(cursor) = prepaint.cursor.take() { - window.paint_quad(cursor); - } + if focus_handle.is_focused(window) + && let Some(cursor) = prepaint.cursor.take() + { + window.paint_quad(cursor); } self.input.update(cx, |input, _cx| { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index e1df6d0be4..bbd59fa7bc 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -368,7 +368,7 @@ impl App { }), }); - init_app_menus(platform.as_ref(), &mut app.borrow_mut()); + init_app_menus(platform.as_ref(), &app.borrow()); platform.on_keyboard_layout_change(Box::new({ let app = Rc::downgrade(&app); @@ -1310,7 +1310,7 @@ impl App { T: 'static, { let window_handle = window.handle; - self.observe_release(&handle, move |entity, cx| { + self.observe_release(handle, move |entity, cx| { let _ = window_handle.update(cx, |_, window, cx| on_release(entity, window, cx)); }) } @@ -1332,7 +1332,7 @@ impl App { } inner( - &mut self.keystroke_observers, + &self.keystroke_observers, Box::new(move |event, window, cx| { f(event, window, cx); true @@ -1358,7 +1358,7 @@ impl App { } inner( - &mut self.keystroke_interceptors, + &self.keystroke_interceptors, Box::new(move |event, window, cx| { f(event, window, cx); true @@ -1516,12 +1516,11 @@ impl App { /// the bindings in the element tree, and any global action listeners. pub fn is_action_available(&mut self, action: &dyn Action) -> bool { let mut action_available = false; - if let Some(window) = self.active_window() { - if let Ok(window_action_available) = + if let Some(window) = self.active_window() + && let Ok(window_action_available) = window.update(self, |_, window, cx| window.is_action_available(action, cx)) - { - action_available = window_action_available; - } + { + action_available = window_action_available; } action_available @@ -1606,27 +1605,26 @@ impl App { .insert(action.as_any().type_id(), global_listeners); } - if self.propagate_event { - if let Some(mut global_listeners) = self + if self.propagate_event + && let Some(mut global_listeners) = self .global_action_listeners .remove(&action.as_any().type_id()) - { - for listener in global_listeners.iter().rev() { - listener(action.as_any(), DispatchPhase::Bubble, self); - if !self.propagate_event { - break; - } + { + for listener in global_listeners.iter().rev() { + listener(action.as_any(), DispatchPhase::Bubble, self); + if !self.propagate_event { + break; } - - global_listeners.extend( - self.global_action_listeners - .remove(&action.as_any().type_id()) - .unwrap_or_default(), - ); - - self.global_action_listeners - .insert(action.as_any().type_id(), global_listeners); } + + global_listeners.extend( + self.global_action_listeners + .remove(&action.as_any().type_id()) + .unwrap_or_default(), + ); + + self.global_action_listeners + .insert(action.as_any().type_id(), global_listeners); } } @@ -1709,8 +1707,8 @@ impl App { .unwrap_or_else(|| { is_first = true; let future = A::load(source.clone(), self); - let task = self.background_executor().spawn(future).shared(); - task + + self.background_executor().spawn(future).shared() }); self.loading_assets.insert(asset_id, Box::new(task.clone())); @@ -1917,7 +1915,7 @@ impl AppContext for App { G: Global, { let mut g = self.global::<G>(); - callback(&g, self) + callback(g, self) } } diff --git a/crates/gpui/src/app/context.rs b/crates/gpui/src/app/context.rs index 68c41592b3..1112878a66 100644 --- a/crates/gpui/src/app/context.rs +++ b/crates/gpui/src/app/context.rs @@ -472,7 +472,7 @@ impl<'a, T: 'static> Context<'a, T> { let view = self.weak_entity(); inner( - &mut self.keystroke_observers, + &self.keystroke_observers, Box::new(move |event, window, cx| { if let Some(view) = view.upgrade() { view.update(cx, |view, cx| f(view, event, window, cx)); @@ -610,16 +610,16 @@ impl<'a, T: 'static> Context<'a, T> { let (subscription, activate) = window.new_focus_listener(Box::new(move |event, window, cx| { view.update(cx, |view, cx| { - if let Some(blurred_id) = event.previous_focus_path.last().copied() { - if event.is_focus_out(focus_id) { - let event = FocusOutEvent { - blurred: WeakFocusHandle { - id: blurred_id, - handles: Arc::downgrade(&cx.focus_handles), - }, - }; - listener(view, event, window, cx) - } + if let Some(blurred_id) = event.previous_focus_path.last().copied() + && event.is_focus_out(focus_id) + { + let event = FocusOutEvent { + blurred: WeakFocusHandle { + id: blurred_id, + handles: Arc::downgrade(&cx.focus_handles), + }, + }; + listener(view, event, window, cx) } }) .is_ok() diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index fccb417caa..6099ee5857 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -661,7 +661,7 @@ pub struct WeakEntity<T> { impl<T> std::fmt::Debug for WeakEntity<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(&type_name::<Self>()) + f.debug_struct(type_name::<Self>()) .field("entity_id", &self.any_entity.entity_id) .field("entity_type", &type_name::<T>()) .finish() @@ -786,7 +786,7 @@ impl<T: 'static> PartialOrd for WeakEntity<T> { #[cfg(any(test, feature = "leak-detection"))] static LEAK_BACKTRACE: std::sync::LazyLock<bool> = - std::sync::LazyLock::new(|| std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty())); + std::sync::LazyLock::new(|| std::env::var("LEAK_BACKTRACE").is_ok_and(|b| !b.is_empty())); #[cfg(any(test, feature = "leak-detection"))] #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index a96c24432a..43adacf7dd 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -219,7 +219,7 @@ impl TestAppContext { let mut cx = self.app.borrow_mut(); // Some tests rely on the window size matching the bounds of the test display - let bounds = Bounds::maximized(None, &mut cx); + let bounds = Bounds::maximized(None, &cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), @@ -233,7 +233,7 @@ impl TestAppContext { /// Adds a new window with no content. pub fn add_empty_window(&mut self) -> &mut VisualTestContext { let mut cx = self.app.borrow_mut(); - let bounds = Bounds::maximized(None, &mut cx); + let bounds = Bounds::maximized(None, &cx); let window = cx .open_window( WindowOptions { @@ -261,7 +261,7 @@ impl TestAppContext { V: 'static + Render, { let mut cx = self.app.borrow_mut(); - let bounds = Bounds::maximized(None, &mut cx); + let bounds = Bounds::maximized(None, &cx); let window = cx .open_window( WindowOptions { diff --git a/crates/gpui/src/arena.rs b/crates/gpui/src/arena.rs index ee72d0e964..0983bd2345 100644 --- a/crates/gpui/src/arena.rs +++ b/crates/gpui/src/arena.rs @@ -142,7 +142,7 @@ impl Arena { if self.current_chunk_index >= self.chunks.len() { self.chunks.push(Chunk::new(self.chunk_size)); assert_eq!(self.current_chunk_index, self.chunks.len() - 1); - log::info!( + log::trace!( "increased element arena capacity to {}kb", self.capacity() / 1024, ); diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index e5f49c7be1..f537bc5ac8 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -603,10 +603,8 @@ impl AnyElement { self.0.prepaint(window, cx); - if !focus_assigned { - if let Some(focus_id) = window.next_frame.focus { - return FocusHandle::for_id(focus_id, &cx.focus_handles); - } + if !focus_assigned && let Some(focus_id) = window.next_frame.focus { + return FocusHandle::for_id(focus_id, &cx.focus_handles); } None diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 0da90882ac..de5b720d46 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -27,6 +27,7 @@ use crate::{ use collections::HashMap; use refineable::Refineable; use smallvec::SmallVec; +use stacksafe::{StackSafe, stacksafe}; use std::{ any::{Any, TypeId}, cell::RefCell, @@ -285,21 +286,20 @@ impl Interactivity { { self.mouse_move_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { - if phase == DispatchPhase::Capture { - if let Some(drag) = &cx.active_drag { - if drag.value.as_ref().type_id() == TypeId::of::<T>() { - (listener)( - &DragMoveEvent { - event: event.clone(), - bounds: hitbox.bounds, - drag: PhantomData, - dragged_item: Arc::clone(&drag.value), - }, - window, - cx, - ); - } - } + if phase == DispatchPhase::Capture + && let Some(drag) = &cx.active_drag + && drag.value.as_ref().type_id() == TypeId::of::<T>() + { + (listener)( + &DragMoveEvent { + event: event.clone(), + bounds: hitbox.bounds, + drag: PhantomData, + dragged_item: Arc::clone(&drag.value), + }, + window, + cx, + ); } })); } @@ -1204,7 +1204,7 @@ pub fn div() -> Div { /// A [`Div`] element, the all-in-one element for building complex UIs in GPUI pub struct Div { interactivity: Interactivity, - children: SmallVec<[AnyElement; 2]>, + children: SmallVec<[StackSafe<AnyElement>; 2]>, prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>, image_cache: Option<Box<dyn ImageCacheProvider>>, } @@ -1265,7 +1265,8 @@ impl InteractiveElement for Div { impl ParentElement for Div { fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) { - self.children.extend(elements) + self.children + .extend(elements.into_iter().map(StackSafe::new)) } } @@ -1281,6 +1282,7 @@ impl Element for Div { self.interactivity.source_location() } + #[stacksafe] fn request_layout( &mut self, global_id: Option<&GlobalElementId>, @@ -1316,6 +1318,7 @@ impl Element for Div { (layout_id, DivFrameState { child_layout_ids }) } + #[stacksafe] fn prepaint( &mut self, global_id: Option<&GlobalElementId>, @@ -1385,6 +1388,7 @@ impl Element for Div { ) } + #[stacksafe] fn paint( &mut self, global_id: Option<&GlobalElementId>, @@ -1518,15 +1522,14 @@ impl Interactivity { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); - if let Some(element_state) = element_state.as_ref() { - if cx.has_active_drag() { - if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() - { - *pending_mouse_down.borrow_mut() = None; - } - if let Some(clicked_state) = element_state.clicked_state.as_ref() { - *clicked_state.borrow_mut() = ElementClickedState::default(); - } + if let Some(element_state) = element_state.as_ref() + && cx.has_active_drag() + { + if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() { + *pending_mouse_down.borrow_mut() = None; + } + if let Some(clicked_state) = element_state.clicked_state.as_ref() { + *clicked_state.borrow_mut() = ElementClickedState::default(); } } @@ -1534,35 +1537,35 @@ impl Interactivity { // If there's an explicit focus handle we're tracking, use that. Otherwise // create a new handle and store it in the element state, which lives for as // as frames contain an element with this id. - if self.focusable && self.tracked_focus_handle.is_none() { - if let Some(element_state) = element_state.as_mut() { - let mut handle = element_state - .focus_handle - .get_or_insert_with(|| cx.focus_handle()) - .clone() - .tab_stop(false); + if self.focusable + && self.tracked_focus_handle.is_none() + && let Some(element_state) = element_state.as_mut() + { + let mut handle = element_state + .focus_handle + .get_or_insert_with(|| cx.focus_handle()) + .clone() + .tab_stop(false); - if let Some(index) = self.tab_index { - handle = handle.tab_index(index).tab_stop(true); - } - - self.tracked_focus_handle = Some(handle); + if let Some(index) = self.tab_index { + handle = handle.tab_index(index).tab_stop(true); } + + self.tracked_focus_handle = Some(handle); } if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() { self.scroll_offset = Some(scroll_handle.0.borrow().offset.clone()); - } else if self.base_style.overflow.x == Some(Overflow::Scroll) - || self.base_style.overflow.y == Some(Overflow::Scroll) + } else if (self.base_style.overflow.x == Some(Overflow::Scroll) + || self.base_style.overflow.y == Some(Overflow::Scroll)) + && let Some(element_state) = element_state.as_mut() { - if let Some(element_state) = element_state.as_mut() { - self.scroll_offset = Some( - element_state - .scroll_offset - .get_or_insert_with(Rc::default) - .clone(), - ); - } + self.scroll_offset = Some( + element_state + .scroll_offset + .get_or_insert_with(Rc::default) + .clone(), + ); } let style = self.compute_style_internal(None, element_state.as_mut(), window, cx); @@ -2035,26 +2038,27 @@ impl Interactivity { let hitbox = hitbox.clone(); window.on_mouse_event({ move |_: &MouseUpEvent, phase, window, cx| { - if let Some(drag) = &cx.active_drag { - if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) { - let drag_state_type = drag.value.as_ref().type_id(); - for (drop_state_type, listener) in &drop_listeners { - if *drop_state_type == drag_state_type { - let drag = cx - .active_drag - .take() - .expect("checked for type drag state type above"); + if let Some(drag) = &cx.active_drag + && phase == DispatchPhase::Bubble + && hitbox.is_hovered(window) + { + let drag_state_type = drag.value.as_ref().type_id(); + for (drop_state_type, listener) in &drop_listeners { + if *drop_state_type == drag_state_type { + let drag = cx + .active_drag + .take() + .expect("checked for type drag state type above"); - let mut can_drop = true; - if let Some(predicate) = &can_drop_predicate { - can_drop = predicate(drag.value.as_ref(), window, cx); - } + let mut can_drop = true; + if let Some(predicate) = &can_drop_predicate { + can_drop = predicate(drag.value.as_ref(), window, cx); + } - if can_drop { - listener(drag.value.as_ref(), window, cx); - window.refresh(); - cx.stop_propagation(); - } + if can_drop { + listener(drag.value.as_ref(), window, cx); + window.refresh(); + cx.stop_propagation(); } } } @@ -2098,31 +2102,24 @@ impl Interactivity { } let mut pending_mouse_down = pending_mouse_down.borrow_mut(); - if let Some(mouse_down) = pending_mouse_down.clone() { - if !cx.has_active_drag() - && (event.position - mouse_down.position).magnitude() - > DRAG_THRESHOLD - { - if let Some((drag_value, drag_listener)) = drag_listener.take() { - *clicked_state.borrow_mut() = ElementClickedState::default(); - let cursor_offset = event.position - hitbox.origin; - let drag = (drag_listener)( - drag_value.as_ref(), - cursor_offset, - window, - cx, - ); - cx.active_drag = Some(AnyDrag { - view: drag, - value: drag_value, - cursor_offset, - cursor_style: drag_cursor_style, - }); - pending_mouse_down.take(); - window.refresh(); - cx.stop_propagation(); - } - } + if let Some(mouse_down) = pending_mouse_down.clone() + && !cx.has_active_drag() + && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD + && let Some((drag_value, drag_listener)) = drag_listener.take() + { + *clicked_state.borrow_mut() = ElementClickedState::default(); + let cursor_offset = event.position - hitbox.origin; + let drag = + (drag_listener)(drag_value.as_ref(), cursor_offset, window, cx); + cx.active_drag = Some(AnyDrag { + view: drag, + value: drag_value, + cursor_offset, + cursor_style: drag_cursor_style, + }); + pending_mouse_down.take(); + window.refresh(); + cx.stop_propagation(); } } }); @@ -2286,7 +2283,7 @@ impl Interactivity { window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| { if phase == DispatchPhase::Bubble && !window.default_prevented() { let group_hovered = active_group_hitbox - .map_or(false, |group_hitbox_id| group_hitbox_id.is_hovered(window)); + .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window)); let element_hovered = hitbox.is_hovered(window); if group_hovered || element_hovered { *active_state.borrow_mut() = ElementClickedState { @@ -2432,33 +2429,32 @@ impl Interactivity { style.refine(&self.base_style); if let Some(focus_handle) = self.tracked_focus_handle.as_ref() { - if let Some(in_focus_style) = self.in_focus_style.as_ref() { - if focus_handle.within_focused(window, cx) { - style.refine(in_focus_style); - } + if let Some(in_focus_style) = self.in_focus_style.as_ref() + && focus_handle.within_focused(window, cx) + { + style.refine(in_focus_style); } - if let Some(focus_style) = self.focus_style.as_ref() { - if focus_handle.is_focused(window) { - style.refine(focus_style); - } + if let Some(focus_style) = self.focus_style.as_ref() + && focus_handle.is_focused(window) + { + style.refine(focus_style); } } if let Some(hitbox) = hitbox { if !cx.has_active_drag() { - if let Some(group_hover) = self.group_hover_style.as_ref() { - if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) { - if group_hitbox_id.is_hovered(window) { - style.refine(&group_hover.style); - } - } + if let Some(group_hover) = self.group_hover_style.as_ref() + && let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) + && group_hitbox_id.is_hovered(window) + { + style.refine(&group_hover.style); } - if let Some(hover_style) = self.hover_style.as_ref() { - if hitbox.is_hovered(window) { - style.refine(hover_style); - } + if let Some(hover_style) = self.hover_style.as_ref() + && hitbox.is_hovered(window) + { + style.refine(hover_style); } } @@ -2472,12 +2468,10 @@ impl Interactivity { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_hitbox_id) = GroupHitboxes::get(&group_drag_style.group, cx) + && *state_type == drag.value.as_ref().type_id() + && group_hitbox_id.is_hovered(window) { - if *state_type == drag.value.as_ref().type_id() - && group_hitbox_id.is_hovered(window) - { - style.refine(&group_drag_style.style); - } + style.refine(&group_drag_style.style); } } @@ -2499,16 +2493,16 @@ impl Interactivity { .clicked_state .get_or_insert_with(Default::default) .borrow(); - if clicked_state.group { - if let Some(group) = self.group_active_style.as_ref() { - style.refine(&group.style) - } + if clicked_state.group + && let Some(group) = self.group_active_style.as_ref() + { + style.refine(&group.style) } - if let Some(active_style) = self.active_style.as_ref() { - if clicked_state.element { - style.refine(active_style) - } + if let Some(active_style) = self.active_style.as_ref() + && clicked_state.element + { + style.refine(active_style) } } @@ -2629,7 +2623,7 @@ pub(crate) fn register_tooltip_mouse_handlers( window.on_mouse_event({ let active_tooltip = active_tooltip.clone(); move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| { - if !tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(window)) { + if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) { clear_active_tooltip_if_not_hoverable(&active_tooltip, window); } } @@ -2638,7 +2632,7 @@ pub(crate) fn register_tooltip_mouse_handlers( window.on_mouse_event({ let active_tooltip = active_tooltip.clone(); move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| { - if !tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(window)) { + if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) { clear_active_tooltip_if_not_hoverable(&active_tooltip, window); } } @@ -2794,7 +2788,7 @@ fn handle_tooltip_check_visible_and_update( match action { Action::None => {} - Action::Hide => clear_active_tooltip(&active_tooltip, window), + Action::Hide => clear_active_tooltip(active_tooltip, window), Action::ScheduleHide(tooltip) => { let delayed_hide_task = window.spawn(cx, { let active_tooltip = active_tooltip.clone(); diff --git a/crates/gpui/src/elements/image_cache.rs b/crates/gpui/src/elements/image_cache.rs index e7bdeaf9eb..ee1436134a 100644 --- a/crates/gpui/src/elements/image_cache.rs +++ b/crates/gpui/src/elements/image_cache.rs @@ -64,7 +64,7 @@ mod any_image_cache { cx: &mut App, ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> { let image_cache = image_cache.clone().downcast::<I>().unwrap(); - return image_cache.update(cx, |image_cache, cx| image_cache.load(resource, window, cx)); + image_cache.update(cx, |image_cache, cx| image_cache.load(resource, window, cx)) } } @@ -297,10 +297,10 @@ impl RetainAllImageCache { /// Remove the image from the cache by the given source. pub fn remove(&mut self, source: &Resource, window: &mut Window, cx: &mut App) { let hash = hash(source); - if let Some(mut item) = self.0.remove(&hash) { - if let Some(Ok(image)) = item.get() { - cx.drop_image(image, Some(window)); - } + if let Some(mut item) = self.0.remove(&hash) + && let Some(Ok(image)) = item.get() + { + cx.drop_image(image, Some(window)); } } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 993b319b69..ae63819ca2 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -379,13 +379,12 @@ impl Element for Img { None => { if let Some(state) = &mut state { if let Some((started_loading, _)) = state.started_loading { - if started_loading.elapsed() > LOADING_DELAY { - if let Some(loading) = self.style.loading.as_ref() { - let mut element = loading(); - replacement_id = - Some(element.request_layout(window, cx)); - layout_state.replacement = Some(element); - } + if started_loading.elapsed() > LOADING_DELAY + && let Some(loading) = self.style.loading.as_ref() + { + let mut element = loading(); + replacement_id = Some(element.request_layout(window, cx)); + layout_state.replacement = Some(element); } } else { let current_view = window.current_view(); diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 39f38bdc69..6758f4eee1 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -732,46 +732,44 @@ impl StateInner { item.element.prepaint_at(item_origin, window, cx); }); - if let Some(autoscroll_bounds) = window.take_autoscroll() { - if autoscroll { - if autoscroll_bounds.top() < bounds.top() { - return Err(ListOffset { - item_ix: item.index, - offset_in_item: autoscroll_bounds.top() - item_origin.y, - }); - } else if autoscroll_bounds.bottom() > bounds.bottom() { - let mut cursor = self.items.cursor::<Count>(&()); - cursor.seek(&Count(item.index), Bias::Right); - let mut height = bounds.size.height - padding.top - padding.bottom; - - // Account for the height of the element down until the autoscroll bottom. - height -= autoscroll_bounds.bottom() - item_origin.y; - - // Keep decreasing the scroll top until we fill all the available space. - while height > Pixels::ZERO { - cursor.prev(); - let Some(item) = cursor.item() else { break }; - - let size = item.size().unwrap_or_else(|| { - let mut item = render_item(cursor.start().0, window, cx); - let item_available_size = size( - bounds.size.width.into(), - AvailableSpace::MinContent, - ); - item.layout_as_root(item_available_size, window, cx) - }); - height -= size.height; - } - - return Err(ListOffset { - item_ix: cursor.start().0, - offset_in_item: if height < Pixels::ZERO { - -height - } else { - Pixels::ZERO - }, + if let Some(autoscroll_bounds) = window.take_autoscroll() + && autoscroll + { + if autoscroll_bounds.top() < bounds.top() { + return Err(ListOffset { + item_ix: item.index, + offset_in_item: autoscroll_bounds.top() - item_origin.y, + }); + } else if autoscroll_bounds.bottom() > bounds.bottom() { + let mut cursor = self.items.cursor::<Count>(&()); + cursor.seek(&Count(item.index), Bias::Right); + let mut height = bounds.size.height - padding.top - padding.bottom; + + // Account for the height of the element down until the autoscroll bottom. + height -= autoscroll_bounds.bottom() - item_origin.y; + + // Keep decreasing the scroll top until we fill all the available space. + while height > Pixels::ZERO { + cursor.prev(); + let Some(item) = cursor.item() else { break }; + + let size = item.size().unwrap_or_else(|| { + let mut item = render_item(cursor.start().0, window, cx); + let item_available_size = + size(bounds.size.width.into(), AvailableSpace::MinContent); + item.layout_as_root(item_available_size, window, cx) }); + height -= size.height; } + + return Err(ListOffset { + item_ix: cursor.start().0, + offset_in_item: if height < Pixels::ZERO { + -height + } else { + Pixels::ZERO + }, + }); } } @@ -940,9 +938,10 @@ impl Element for List { let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal); // If the width of the list has changed, invalidate all cached item heights - if state.last_layout_bounds.map_or(true, |last_bounds| { - last_bounds.size.width != bounds.size.width - }) { + if state + .last_layout_bounds + .is_none_or(|last_bounds| last_bounds.size.width != bounds.size.width) + { let new_items = SumTree::from_iter( state.items.iter().map(|item| ListItem::Unmeasured { focus_handle: item.focus_handle(), diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 014f617e2c..b5e0712796 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -326,7 +326,7 @@ impl TextLayout { vec![text_style.to_run(text.len())] }; - let layout_id = window.request_measured_layout(Default::default(), { + window.request_measured_layout(Default::default(), { let element_state = self.clone(); move |known_dimensions, available_space, window, cx| { @@ -356,12 +356,11 @@ impl TextLayout { (None, "".into()) }; - if let Some(text_layout) = element_state.0.borrow().as_ref() { - if text_layout.size.is_some() - && (wrap_width.is_none() || wrap_width == text_layout.wrap_width) - { - return text_layout.size.unwrap(); - } + if let Some(text_layout) = element_state.0.borrow().as_ref() + && text_layout.size.is_some() + && (wrap_width.is_none() || wrap_width == text_layout.wrap_width) + { + return text_layout.size.unwrap(); } let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size); @@ -417,9 +416,7 @@ impl TextLayout { size } - }); - - layout_id + }) } fn prepaint(&self, bounds: Bounds<Pixels>, text: &str) { @@ -763,14 +760,13 @@ impl Element for InteractiveText { let mut interactive_state = interactive_state.unwrap_or_default(); if let Some(click_listener) = self.click_listener.take() { let mouse_position = window.mouse_position(); - if let Ok(ix) = text_layout.index_for_position(mouse_position) { - if self + if let Ok(ix) = text_layout.index_for_position(mouse_position) + && self .clickable_ranges .iter() .any(|range| range.contains(&ix)) - { - window.set_cursor_style(crate::CursorStyle::PointingHand, hitbox) - } + { + window.set_cursor_style(crate::CursorStyle::PointingHand, hitbox) } let text_layout = text_layout.clone(); @@ -803,13 +799,13 @@ impl Element for InteractiveText { } else { let hitbox = hitbox.clone(); window.on_mouse_event(move |event: &MouseDownEvent, phase, window, _| { - if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) { - if let Ok(mouse_down_index) = + if phase == DispatchPhase::Bubble + && hitbox.is_hovered(window) + && let Ok(mouse_down_index) = text_layout.index_for_position(event.position) - { - mouse_down.set(Some(mouse_down_index)); - window.refresh(); - } + { + mouse_down.set(Some(mouse_down_index)); + window.refresh(); } }); } diff --git a/crates/gpui/src/inspector.rs b/crates/gpui/src/inspector.rs index 23c46edcc1..9f86576a59 100644 --- a/crates/gpui/src/inspector.rs +++ b/crates/gpui/src/inspector.rs @@ -164,7 +164,7 @@ mod conditional { if let Some(render_inspector) = cx .inspector_element_registry .renderers_by_type_id - .remove(&type_id) + .remove(type_id) { let mut element = (render_inspector)( active_element.id.clone(), diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index c3f5d18603..95374e579f 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -408,7 +408,7 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - Self::binding_matches_predicate_and_not_shadowed(&keymap, &binding, context_stack) + Self::binding_matches_predicate_and_not_shadowed(&keymap, binding, context_stack) }) .cloned() .collect() @@ -426,7 +426,7 @@ impl DispatchTree { .bindings_for_action(action) .rev() .find(|binding| { - Self::binding_matches_predicate_and_not_shadowed(&keymap, &binding, context_stack) + Self::binding_matches_predicate_and_not_shadowed(&keymap, binding, context_stack) }) .cloned() } @@ -458,7 +458,7 @@ impl DispatchTree { .keymap .borrow() .bindings_for_input(input, &context_stack); - return (bindings, partial, context_stack); + (bindings, partial, context_stack) } /// dispatch_key processes the keystroke @@ -639,10 +639,7 @@ mod tests { } fn partial_eq(&self, action: &dyn Action) -> bool { - action - .as_any() - .downcast_ref::<Self>() - .map_or(false, |a| self == a) + action.as_any().downcast_ref::<Self>() == Some(self) } fn boxed_clone(&self) -> std::boxed::Box<dyn Action> { diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index 83d7479a04..66f191ca5d 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -148,7 +148,7 @@ impl Keymap { let mut pending_bindings = SmallVec::<[(BindingIndex, &KeyBinding); 1]>::new(); for (ix, binding) in self.bindings().enumerate().rev() { - let Some(depth) = self.binding_enabled(binding, &context_stack) else { + let Some(depth) = self.binding_enabled(binding, context_stack) else { continue; }; let Some(pending) = binding.match_keystrokes(input) else { diff --git a/crates/gpui/src/keymap/binding.rs b/crates/gpui/src/keymap/binding.rs index 1d3f612c5b..6d36cbb4e0 100644 --- a/crates/gpui/src/keymap/binding.rs +++ b/crates/gpui/src/keymap/binding.rs @@ -53,10 +53,10 @@ impl KeyBinding { if let Some(equivalents) = key_equivalents { for keystroke in keystrokes.iter_mut() { - if keystroke.key.chars().count() == 1 { - if let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap()) { - keystroke.key = key.to_string(); - } + if keystroke.key.chars().count() == 1 + && let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap()) + { + keystroke.key = key.to_string(); } } } diff --git a/crates/gpui/src/keymap/context.rs b/crates/gpui/src/keymap/context.rs index 281035fe97..976f99c26e 100644 --- a/crates/gpui/src/keymap/context.rs +++ b/crates/gpui/src/keymap/context.rs @@ -287,7 +287,7 @@ impl KeyBindingContextPredicate { return false; } } - return true; + true } // Workspace > Pane > Editor // @@ -305,7 +305,7 @@ impl KeyBindingContextPredicate { return true; } } - return false; + false } Self::And(left, right) => { left.eval_inner(contexts, all_contexts) && right.eval_inner(contexts, all_contexts) diff --git a/crates/gpui/src/path_builder.rs b/crates/gpui/src/path_builder.rs index 6c8cfddd52..38903ea588 100644 --- a/crates/gpui/src/path_builder.rs +++ b/crates/gpui/src/path_builder.rs @@ -278,7 +278,7 @@ impl PathBuilder { options: &StrokeOptions, ) -> Result<Path<Pixels>, Error> { let path = if let Some(dash_array) = dash_array { - let measurements = lyon::algorithms::measure::PathMeasurements::from_path(&path, 0.01); + let measurements = lyon::algorithms::measure::PathMeasurements::from_path(path, 0.01); let mut sampler = measurements .create_sampler(path, lyon::algorithms::measure::SampleType::Normalized); let mut builder = lyon::path::Path::builder(); diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index bf6ce68703..1df8a608f4 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -592,7 +592,7 @@ impl PlatformTextSystem for NoopTextSystem { } fn font_id(&self, _descriptor: &Font) -> Result<FontId> { - return Ok(FontId(1)); + Ok(FontId(1)) } fn font_metrics(&self, _font_id: FontId) -> FontMetrics { @@ -1278,7 +1278,7 @@ pub enum WindowBackgroundAppearance { } /// The options that can be configured for a file dialog prompt -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub struct PathPromptOptions { /// Should the prompt allow files to be selected? pub files: bool, @@ -1286,6 +1286,8 @@ pub struct PathPromptOptions { pub directories: bool, /// Should the prompt allow multiple files to be selected? pub multiple: bool, + /// The prompt to show to a user when selecting a path + pub prompt: Option<SharedString>, } /// What kind of prompt styling to show @@ -1506,7 +1508,7 @@ impl ClipboardItem { for entry in self.entries.iter() { if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry { - answer.push_str(&text); + answer.push_str(text); any_entries = true; } } diff --git a/crates/gpui/src/platform/blade/blade_context.rs b/crates/gpui/src/platform/blade/blade_context.rs index 48872f1619..12c68a1e70 100644 --- a/crates/gpui/src/platform/blade/blade_context.rs +++ b/crates/gpui/src/platform/blade/blade_context.rs @@ -49,7 +49,7 @@ fn parse_pci_id(id: &str) -> anyhow::Result<u32> { "Expected a 4 digit PCI ID in hexadecimal format" ); - return u32::from_str_radix(id, 16).context("parsing PCI ID as hex"); + u32::from_str_radix(id, 16).context("parsing PCI ID as hex") } #[cfg(test)] diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index 46d3c16c72..cc1df7748b 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/crates/gpui/src/platform/blade/blade_renderer.rs @@ -434,24 +434,24 @@ impl BladeRenderer { } fn wait_for_gpu(&mut self) { - if let Some(last_sp) = self.last_sync_point.take() { - if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) { - log::error!("GPU hung"); - #[cfg(target_os = "linux")] - if self.gpu.device_information().driver_name == "radv" { - log::error!( - "there's a known bug with amdgpu/radv, try setting ZED_PATH_SAMPLE_COUNT=0 as a workaround" - ); - log::error!( - "if that helps you're running into https://github.com/zed-industries/zed/issues/26143" - ); - } + if let Some(last_sp) = self.last_sync_point.take() + && !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) + { + log::error!("GPU hung"); + #[cfg(target_os = "linux")] + if self.gpu.device_information().driver_name == "radv" { log::error!( - "your device information is: {:?}", - self.gpu.device_information() + "there's a known bug with amdgpu/radv, try setting ZED_PATH_SAMPLE_COUNT=0 as a workaround" + ); + log::error!( + "if that helps you're running into https://github.com/zed-industries/zed/issues/26143" ); - while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {} } + log::error!( + "your device information is: {:?}", + self.gpu.device_information() + ); + while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {} } } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 31d445be52..ed824744a9 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -294,6 +294,7 @@ impl<P: LinuxClient + 'static> Platform for P { let request = match ashpd::desktop::file_chooser::OpenFileRequest::default() .modal(true) .title(title) + .accept_label(options.prompt.as_ref().map(crate::SharedString::as_str)) .multiple(options.multiple) .directory(options.directories) .send() @@ -440,7 +441,7 @@ impl<P: LinuxClient + 'static> Platform for P { fn app_path(&self) -> Result<PathBuf> { // get the path of the executable of the current process let app_path = env::current_exe()?; - return Ok(app_path); + Ok(app_path) } fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) { @@ -641,7 +642,7 @@ pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::S let mut state: Option<xkb::compose::State> = None; for locale in locales { if let Ok(table) = - xkb::compose::Table::new_from_locale(&cx, &locale, xkb::compose::COMPILE_NO_FLAGS) + xkb::compose::Table::new_from_locale(cx, &locale, xkb::compose::COMPILE_NO_FLAGS) { state = Some(xkb::compose::State::new( &table, diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 72e4477ecf..4d31428094 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -359,13 +359,13 @@ impl WaylandClientStatePtr { } changed }; - if changed { - if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() { - drop(state); - callback(); - state = client.borrow_mut(); - state.common.callbacks.keyboard_layout_change = Some(callback); - } + + if changed && let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() + { + drop(state); + callback(); + state = client.borrow_mut(); + state.common.callbacks.keyboard_layout_change = Some(callback); } } @@ -373,15 +373,15 @@ impl WaylandClientStatePtr { let mut client = self.get_client(); let mut state = client.borrow_mut(); let closed_window = state.windows.remove(surface_id).unwrap(); - if let Some(window) = state.mouse_focused_window.take() { - if !window.ptr_eq(&closed_window) { - state.mouse_focused_window = Some(window); - } + if let Some(window) = state.mouse_focused_window.take() + && !window.ptr_eq(&closed_window) + { + state.mouse_focused_window = Some(window); } - if let Some(window) = state.keyboard_focused_window.take() { - if !window.ptr_eq(&closed_window) { - state.keyboard_focused_window = Some(window); - } + if let Some(window) = state.keyboard_focused_window.take() + && !window.ptr_eq(&closed_window) + { + state.keyboard_focused_window = Some(window); } if state.windows.is_empty() { state.common.signal.stop(); @@ -710,9 +710,7 @@ impl LinuxClient for WaylandClient { fn set_cursor_style(&self, style: CursorStyle) { let mut state = self.0.borrow_mut(); - let need_update = state - .cursor_style - .map_or(true, |current_style| current_style != style); + let need_update = state.cursor_style != Some(style); if need_update { let serial = state.serial_tracker.get(SerialKind::MouseEnter); @@ -951,11 +949,8 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr { }; drop(state); - match event { - wl_callback::Event::Done { .. } => { - window.frame(); - } - _ => {} + if let wl_callback::Event::Done { .. } = event { + window.frame(); } } } @@ -1145,7 +1140,7 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr { .globals .text_input_manager .as_ref() - .map(|text_input_manager| text_input_manager.get_text_input(&seat, qh, ())); + .map(|text_input_manager| text_input_manager.get_text_input(seat, qh, ())); if let Some(wl_keyboard) = &state.wl_keyboard { wl_keyboard.release(); @@ -1294,7 +1289,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr { match key_state { wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => { let mut keystroke = - Keystroke::from_xkb(&keymap_state, state.modifiers, keycode); + Keystroke::from_xkb(keymap_state, state.modifiers, keycode); if let Some(mut compose) = state.compose_state.take() { compose.feed(keysym); match compose.status() { @@ -1538,12 +1533,9 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr { cursor_shape_device.set_shape(serial, style.to_shape()); } else { let scale = window.primary_output_scale(); - state.cursor.set_icon( - &wl_pointer, - serial, - style.to_icon_names(), - scale, - ); + state + .cursor + .set_icon(wl_pointer, serial, style.to_icon_names(), scale); } } drop(state); @@ -1580,7 +1572,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr { if state .keyboard_focused_window .as_ref() - .map_or(false, |keyboard_window| window.ptr_eq(&keyboard_window)) + .is_some_and(|keyboard_window| window.ptr_eq(keyboard_window)) { state.enter_token = None; } @@ -1787,17 +1779,17 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr { drop(state); window.handle_input(input); } - } else if let Some(discrete) = discrete { - if let Some(window) = state.mouse_focused_window.clone() { - let input = PlatformInput::ScrollWheel(ScrollWheelEvent { - position: state.mouse_location.unwrap(), - delta: ScrollDelta::Lines(discrete), - modifiers: state.modifiers, - touch_phase: TouchPhase::Moved, - }); - drop(state); - window.handle_input(input); - } + } else if let Some(discrete) = discrete + && let Some(window) = state.mouse_focused_window.clone() + { + let input = PlatformInput::ScrollWheel(ScrollWheelEvent { + position: state.mouse_location.unwrap(), + delta: ScrollDelta::Lines(discrete), + modifiers: state.modifiers, + touch_phase: TouchPhase::Moved, + }); + drop(state); + window.handle_input(input); } } } @@ -2019,25 +2011,22 @@ impl Dispatch<wl_data_offer::WlDataOffer, ()> for WaylandClientStatePtr { let client = this.get_client(); let mut state = client.borrow_mut(); - match event { - wl_data_offer::Event::Offer { mime_type } => { - // Drag and drop - if mime_type == FILE_LIST_MIME_TYPE { - let serial = state.serial_tracker.get(SerialKind::DataDevice); - let mime_type = mime_type.clone(); - data_offer.accept(serial, Some(mime_type)); - } - - // Clipboard - if let Some(offer) = state - .data_offers - .iter_mut() - .find(|wrapper| wrapper.inner.id() == data_offer.id()) - { - offer.add_mime_type(mime_type); - } + if let wl_data_offer::Event::Offer { mime_type } = event { + // Drag and drop + if mime_type == FILE_LIST_MIME_TYPE { + let serial = state.serial_tracker.get(SerialKind::DataDevice); + let mime_type = mime_type.clone(); + data_offer.accept(serial, Some(mime_type)); + } + + // Clipboard + if let Some(offer) = state + .data_offers + .iter_mut() + .find(|wrapper| wrapper.inner.id() == data_offer.id()) + { + offer.add_mime_type(mime_type); } - _ => {} } } } @@ -2118,13 +2107,10 @@ impl Dispatch<zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1, ()> let client = this.get_client(); let mut state = client.borrow_mut(); - match event { - zwp_primary_selection_offer_v1::Event::Offer { mime_type } => { - if let Some(offer) = state.primary_data_offer.as_mut() { - offer.add_mime_type(mime_type); - } - } - _ => {} + if let zwp_primary_selection_offer_v1::Event::Offer { mime_type } = event + && let Some(offer) = state.primary_data_offer.as_mut() + { + offer.add_mime_type(mime_type); } } } diff --git a/crates/gpui/src/platform/linux/wayland/cursor.rs b/crates/gpui/src/platform/linux/wayland/cursor.rs index 2a24d0e1ba..a21263ccfe 100644 --- a/crates/gpui/src/platform/linux/wayland/cursor.rs +++ b/crates/gpui/src/platform/linux/wayland/cursor.rs @@ -45,10 +45,11 @@ impl Cursor { } fn set_theme_internal(&mut self, theme_name: Option<String>) { - if let Some(loaded_theme) = self.loaded_theme.as_ref() { - if loaded_theme.name == theme_name && loaded_theme.scaled_size == self.scaled_size { - return; - } + if let Some(loaded_theme) = self.loaded_theme.as_ref() + && loaded_theme.name == theme_name + && loaded_theme.scaled_size == self.scaled_size + { + return; } let result = if let Some(theme_name) = theme_name.as_ref() { CursorTheme::load_from_name( @@ -144,7 +145,7 @@ impl Cursor { hot_y as i32 / scale, ); - self.surface.attach(Some(&buffer), 0, 0); + self.surface.attach(Some(buffer), 0, 0); self.surface.damage(0, 0, width as i32, height as i32); self.surface.commit(); } diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 2b2207e22c..ce1468335d 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -355,85 +355,82 @@ impl WaylandWindowStatePtr { } pub fn handle_xdg_surface_event(&self, event: xdg_surface::Event) { - match event { - xdg_surface::Event::Configure { serial } => { - { - let mut state = self.state.borrow_mut(); - if let Some(window_controls) = state.in_progress_window_controls.take() { - state.window_controls = window_controls; - - drop(state); - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() { - appearance_changed(); - } - } - } - { - let mut state = self.state.borrow_mut(); - - if let Some(mut configure) = state.in_progress_configure.take() { - let got_unmaximized = state.maximized && !configure.maximized; - state.fullscreen = configure.fullscreen; - state.maximized = configure.maximized; - state.tiling = configure.tiling; - // Limit interactive resizes to once per vblank - if configure.resizing && state.resize_throttle { - return; - } else if configure.resizing { - state.resize_throttle = true; - } - if !configure.fullscreen && !configure.maximized { - configure.size = if got_unmaximized { - Some(state.window_bounds.size) - } else { - compute_outer_size(state.inset(), configure.size, state.tiling) - }; - if let Some(size) = configure.size { - state.window_bounds = Bounds { - origin: Point::default(), - size, - }; - } - } - drop(state); - if let Some(size) = configure.size { - self.resize(size); - } - } - } + if let xdg_surface::Event::Configure { serial } = event { + { let mut state = self.state.borrow_mut(); - state.xdg_surface.ack_configure(serial); + if let Some(window_controls) = state.in_progress_window_controls.take() { + state.window_controls = window_controls; - let window_geometry = inset_by_tiling( - state.bounds.map_origin(|_| px(0.0)), - state.inset(), - state.tiling, - ) - .map(|v| v.0 as i32) - .map_size(|v| if v <= 0 { 1 } else { v }); - - state.xdg_surface.set_window_geometry( - window_geometry.origin.x, - window_geometry.origin.y, - window_geometry.size.width, - window_geometry.size.height, - ); - - let request_frame_callback = !state.acknowledged_first_configure; - if request_frame_callback { - state.acknowledged_first_configure = true; drop(state); - self.frame(); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() { + appearance_changed(); + } } } - _ => {} + { + let mut state = self.state.borrow_mut(); + + if let Some(mut configure) = state.in_progress_configure.take() { + let got_unmaximized = state.maximized && !configure.maximized; + state.fullscreen = configure.fullscreen; + state.maximized = configure.maximized; + state.tiling = configure.tiling; + // Limit interactive resizes to once per vblank + if configure.resizing && state.resize_throttle { + return; + } else if configure.resizing { + state.resize_throttle = true; + } + if !configure.fullscreen && !configure.maximized { + configure.size = if got_unmaximized { + Some(state.window_bounds.size) + } else { + compute_outer_size(state.inset(), configure.size, state.tiling) + }; + if let Some(size) = configure.size { + state.window_bounds = Bounds { + origin: Point::default(), + size, + }; + } + } + drop(state); + if let Some(size) = configure.size { + self.resize(size); + } + } + } + let mut state = self.state.borrow_mut(); + state.xdg_surface.ack_configure(serial); + + let window_geometry = inset_by_tiling( + state.bounds.map_origin(|_| px(0.0)), + state.inset(), + state.tiling, + ) + .map(|v| v.0 as i32) + .map_size(|v| if v <= 0 { 1 } else { v }); + + state.xdg_surface.set_window_geometry( + window_geometry.origin.x, + window_geometry.origin.y, + window_geometry.size.width, + window_geometry.size.height, + ); + + let request_frame_callback = !state.acknowledged_first_configure; + if request_frame_callback { + state.acknowledged_first_configure = true; + drop(state); + self.frame(); + } } } pub fn handle_toplevel_decoration_event(&self, event: zxdg_toplevel_decoration_v1::Event) { - match event { - zxdg_toplevel_decoration_v1::Event::Configure { mode } => match mode { + if let zxdg_toplevel_decoration_v1::Event::Configure { mode } = event { + match mode { WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => { self.state.borrow_mut().decorations = WindowDecorations::Server; if let Some(mut appearance_changed) = @@ -457,17 +454,13 @@ impl WaylandWindowStatePtr { WEnum::Unknown(v) => { log::warn!("Unknown decoration mode: {}", v); } - }, - _ => {} + } } } pub fn handle_fractional_scale_event(&self, event: wp_fractional_scale_v1::Event) { - match event { - wp_fractional_scale_v1::Event::PreferredScale { scale } => { - self.rescale(scale as f32 / 120.0); - } - _ => {} + if let wp_fractional_scale_v1::Event::PreferredScale { scale } = event { + self.rescale(scale as f32 / 120.0); } } @@ -669,8 +662,8 @@ impl WaylandWindowStatePtr { pub fn set_size_and_scale(&self, size: Option<Size<Pixels>>, scale: Option<f32>) { let (size, scale) = { let mut state = self.state.borrow_mut(); - if size.map_or(true, |size| size == state.bounds.size) - && scale.map_or(true, |scale| scale == state.scale) + if size.is_none_or(|size| size == state.bounds.size) + && scale.is_none_or(|scale| scale == state.scale) { return; } @@ -713,21 +706,20 @@ impl WaylandWindowStatePtr { } pub fn handle_input(&self, input: PlatformInput) { - if let Some(ref mut fun) = self.callbacks.borrow_mut().input { - if !fun(input.clone()).propagate { - return; - } + if let Some(ref mut fun) = self.callbacks.borrow_mut().input + && !fun(input.clone()).propagate + { + return; } - if let PlatformInput::KeyDown(event) = input { - if event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) { - if let Some(key_char) = &event.keystroke.key_char { - let mut state = self.state.borrow_mut(); - if let Some(mut input_handler) = state.input_handler.take() { - drop(state); - input_handler.replace_text_in_range(None, key_char); - self.state.borrow_mut().input_handler = Some(input_handler); - } - } + if let PlatformInput::KeyDown(event) = input + && event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) + && let Some(key_char) = &event.keystroke.key_char + { + let mut state = self.state.borrow_mut(); + if let Some(mut input_handler) = state.input_handler.take() { + drop(state); + input_handler.replace_text_in_range(None, key_char); + self.state.borrow_mut().input_handler = Some(input_handler); } } } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 053cd0387b..346ba8718b 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -232,15 +232,12 @@ impl X11ClientStatePtr { }; let mut state = client.0.borrow_mut(); - if let Some(window_ref) = state.windows.remove(&x_window) { - match window_ref.refresh_state { - Some(RefreshState::PeriodicRefresh { - event_loop_token, .. - }) => { - state.loop_handle.remove(event_loop_token); - } - _ => {} - } + if let Some(window_ref) = state.windows.remove(&x_window) + && let Some(RefreshState::PeriodicRefresh { + event_loop_token, .. + }) = window_ref.refresh_state + { + state.loop_handle.remove(event_loop_token); } if state.mouse_focused_window == Some(x_window) { state.mouse_focused_window = None; @@ -565,10 +562,10 @@ impl X11Client { events.push(last_keymap_change_event); } - if let Some(last_press) = last_key_press.as_ref() { - if last_press.detail == key_press.detail { - continue; - } + if let Some(last_press) = last_key_press.as_ref() + && last_press.detail == key_press.detail + { + continue; } if let Some(Event::KeyRelease(key_release)) = @@ -876,22 +873,19 @@ impl X11Client { let Some(reply) = reply else { return Some(()); }; - match str::from_utf8(&reply.value) { - Ok(file_list) => { - let paths: SmallVec<[_; 2]> = file_list - .lines() - .filter_map(|path| Url::parse(path).log_err()) - .filter_map(|url| url.to_file_path().log_err()) - .collect(); - let input = PlatformInput::FileDrop(FileDropEvent::Entered { - position: state.xdnd_state.position, - paths: crate::ExternalPaths(paths), - }); - drop(state); - window.handle_input(input); - self.0.borrow_mut().xdnd_state.retrieved = true; - } - Err(_) => {} + if let Ok(file_list) = str::from_utf8(&reply.value) { + let paths: SmallVec<[_; 2]> = file_list + .lines() + .filter_map(|path| Url::parse(path).log_err()) + .filter_map(|url| url.to_file_path().log_err()) + .collect(); + let input = PlatformInput::FileDrop(FileDropEvent::Entered { + position: state.xdnd_state.position, + paths: crate::ExternalPaths(paths), + }); + drop(state); + window.handle_input(input); + self.0.borrow_mut().xdnd_state.retrieved = true; } } Event::ConfigureNotify(event) => { @@ -1212,7 +1206,7 @@ impl X11Client { state = self.0.borrow_mut(); if let Some(mut pointer) = state.pointer_device_states.get_mut(&event.sourceid) { - let scroll_delta = get_scroll_delta_and_update_state(&mut pointer, &event); + let scroll_delta = get_scroll_delta_and_update_state(pointer, &event); drop(state); if let Some(scroll_delta) = scroll_delta { window.handle_input(PlatformInput::ScrollWheel(make_scroll_wheel_event( @@ -1271,7 +1265,7 @@ impl X11Client { Event::XinputDeviceChanged(event) => { let mut state = self.0.borrow_mut(); if let Some(mut pointer) = state.pointer_device_states.get_mut(&event.sourceid) { - reset_pointer_device_scroll_positions(&mut pointer); + reset_pointer_device_scroll_positions(pointer); } } _ => {} @@ -1586,11 +1580,11 @@ impl LinuxClient for X11Client { fn read_from_primary(&self) -> Option<crate::ClipboardItem> { let state = self.0.borrow_mut(); - return state + state .clipboard .get_any(clipboard::ClipboardKind::Primary) .context("X11: Failed to read from clipboard (primary)") - .log_with_level(log::Level::Debug); + .log_with_level(log::Level::Debug) } fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> { @@ -1603,11 +1597,11 @@ impl LinuxClient for X11Client { { return state.clipboard_item.clone(); } - return state + state .clipboard .get_any(clipboard::ClipboardKind::Clipboard) .context("X11: Failed to read from clipboard (clipboard)") - .log_with_level(log::Level::Debug); + .log_with_level(log::Level::Debug) } fn run(&self) { @@ -2010,12 +2004,12 @@ fn check_gtk_frame_extents_supported( } fn xdnd_is_atom_supported(atom: u32, atoms: &XcbAtoms) -> bool { - return atom == atoms.TEXT + atom == atoms.TEXT || atom == atoms.STRING || atom == atoms.UTF8_STRING || atom == atoms.TEXT_PLAIN || atom == atoms.TEXT_PLAIN_UTF8 - || atom == atoms.TextUriList; + || atom == atoms.TextUriList } fn xdnd_get_supported_atom( @@ -2035,16 +2029,15 @@ fn xdnd_get_supported_atom( ), ) .log_with_level(Level::Warn) + && let Some(atoms) = reply.value32() { - if let Some(atoms) = reply.value32() { - for atom in atoms { - if xdnd_is_atom_supported(atom, &supported_atoms) { - return atom; - } + for atom in atoms { + if xdnd_is_atom_supported(atom, supported_atoms) { + return atom; } } } - return 0; + 0 } fn xdnd_send_finished( @@ -2145,7 +2138,7 @@ fn current_pointer_device_states( if pointer_device_states.is_empty() { log::error!("Found no xinput mouse pointers."); } - return Some(pointer_device_states); + Some(pointer_device_states) } /// Returns true if the device is a pointer device. Does not include pointer device groups. @@ -2411,11 +2404,13 @@ fn legacy_get_randr_scale_factor(connection: &XCBConnection, root: u32) -> Optio let mut crtc_infos: HashMap<randr::Crtc, randr::GetCrtcInfoReply> = HashMap::default(); let mut valid_outputs: HashSet<randr::Output> = HashSet::new(); for (crtc, cookie) in crtc_cookies { - if let Ok(reply) = cookie.reply() { - if reply.width > 0 && reply.height > 0 && !reply.outputs.is_empty() { - crtc_infos.insert(crtc, reply.clone()); - valid_outputs.extend(&reply.outputs); - } + if let Ok(reply) = cookie.reply() + && reply.width > 0 + && reply.height > 0 + && !reply.outputs.is_empty() + { + crtc_infos.insert(crtc, reply.clone()); + valid_outputs.extend(&reply.outputs); } } diff --git a/crates/gpui/src/platform/linux/x11/clipboard.rs b/crates/gpui/src/platform/linux/x11/clipboard.rs index 5d42eadaaf..a6f96d38c4 100644 --- a/crates/gpui/src/platform/linux/x11/clipboard.rs +++ b/crates/gpui/src/platform/linux/x11/clipboard.rs @@ -1078,11 +1078,11 @@ impl Clipboard { } else { String::from_utf8(result.bytes).map_err(|_| Error::ConversionFailure)? }; - return Ok(ClipboardItem::new_string(text)); + Ok(ClipboardItem::new_string(text)) } pub fn is_owner(&self, selection: ClipboardKind) -> bool { - return self.inner.is_owner(selection).unwrap_or(false); + self.inner.is_owner(selection).unwrap_or(false) } } @@ -1120,25 +1120,25 @@ impl Drop for Clipboard { log::error!("Failed to flush the clipboard window. Error: {}", e); return; } - if let Some(global_cb) = global_cb { - if let Err(e) = global_cb.server_handle.join() { - // Let's try extracting the error message - let message; - if let Some(msg) = e.downcast_ref::<&'static str>() { - message = Some((*msg).to_string()); - } else if let Some(msg) = e.downcast_ref::<String>() { - message = Some(msg.clone()); - } else { - message = None; - } - if let Some(message) = message { - log::error!( - "The clipboard server thread panicked. Panic message: '{}'", - message, - ); - } else { - log::error!("The clipboard server thread panicked."); - } + if let Some(global_cb) = global_cb + && let Err(e) = global_cb.server_handle.join() + { + // Let's try extracting the error message + let message; + if let Some(msg) = e.downcast_ref::<&'static str>() { + message = Some((*msg).to_string()); + } else if let Some(msg) = e.downcast_ref::<String>() { + message = Some(msg.clone()); + } else { + message = None; + } + if let Some(message) = message { + log::error!( + "The clipboard server thread panicked. Panic message: '{}'", + message, + ); + } else { + log::error!("The clipboard server thread panicked."); } } } diff --git a/crates/gpui/src/platform/linux/x11/event.rs b/crates/gpui/src/platform/linux/x11/event.rs index cd4cef24a3..17bcc908d3 100644 --- a/crates/gpui/src/platform/linux/x11/event.rs +++ b/crates/gpui/src/platform/linux/x11/event.rs @@ -73,8 +73,8 @@ pub(crate) fn get_valuator_axis_index( // valuator present in this event's axisvalues. Axisvalues is ordered from // lowest valuator number to highest, so counting bits before the 1 bit for // this valuator yields the index in axisvalues. - if bit_is_set_in_vec(&valuator_mask, valuator_number) { - Some(popcount_upto_bit_index(&valuator_mask, valuator_number) as usize) + if bit_is_set_in_vec(valuator_mask, valuator_number) { + Some(popcount_upto_bit_index(valuator_mask, valuator_number) as usize) } else { None } @@ -104,7 +104,7 @@ fn bit_is_set_in_vec(bit_vec: &Vec<u32>, bit_index: u16) -> bool { let array_index = bit_index as usize / 32; bit_vec .get(array_index) - .map_or(false, |bits| bit_is_set(*bits, bit_index % 32)) + .is_some_and(|bits| bit_is_set(*bits, bit_index % 32)) } fn bit_is_set(bits: u32, bit_index: u16) -> bool { diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 1a3c323c35..c33d6fa462 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -397,7 +397,7 @@ impl X11WindowState { .display_id .map_or(x_main_screen_index, |did| did.0 as usize); - let visual_set = find_visuals(&xcb, x_screen_index); + let visual_set = find_visuals(xcb, x_screen_index); let visual = match visual_set.transparent { Some(visual) => visual, @@ -515,19 +515,19 @@ impl X11WindowState { xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)), )?; } - if let Some(titlebar) = params.titlebar { - if let Some(title) = titlebar.title { - check_reply( - || "X11 ChangeProperty8 on window title failed.", - xcb.change_property8( - xproto::PropMode::REPLACE, - x_window, - xproto::AtomEnum::WM_NAME, - xproto::AtomEnum::STRING, - title.as_bytes(), - ), - )?; - } + if let Some(titlebar) = params.titlebar + && let Some(title) = titlebar.title + { + check_reply( + || "X11 ChangeProperty8 on window title failed.", + xcb.change_property8( + xproto::PropMode::REPLACE, + x_window, + xproto::AtomEnum::WM_NAME, + xproto::AtomEnum::STRING, + title.as_bytes(), + ), + )?; } if params.kind == WindowKind::PopUp { check_reply( @@ -604,7 +604,7 @@ impl X11WindowState { ), )?; - xcb_flush(&xcb); + xcb_flush(xcb); let renderer = { let raw_window = RawWindow { @@ -664,7 +664,7 @@ impl X11WindowState { || "X11 DestroyWindow failed while cleaning it up after setup failure.", xcb.destroy_window(x_window), )?; - xcb_flush(&xcb); + xcb_flush(xcb); } setup_result @@ -956,10 +956,10 @@ impl X11WindowStatePtr { } pub fn handle_input(&self, input: PlatformInput) { - if let Some(ref mut fun) = self.callbacks.borrow_mut().input { - if !fun(input.clone()).propagate { - return; - } + if let Some(ref mut fun) = self.callbacks.borrow_mut().input + && !fun(input.clone()).propagate + { + return; } if let PlatformInput::KeyDown(event) = input { // only allow shift modifier when inserting text @@ -1068,15 +1068,14 @@ impl X11WindowStatePtr { } let mut callbacks = self.callbacks.borrow_mut(); - if let Some((content_size, scale_factor)) = resize_args { - if let Some(ref mut fun) = callbacks.resize { - fun(content_size, scale_factor) - } + if let Some((content_size, scale_factor)) = resize_args + && let Some(ref mut fun) = callbacks.resize + { + fun(content_size, scale_factor) } - if !is_resize { - if let Some(ref mut fun) = callbacks.moved { - fun(); - } + + if !is_resize && let Some(ref mut fun) = callbacks.moved { + fun(); } Ok(()) diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index 0dc361b9dc..938db4b762 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -311,9 +311,8 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask); let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask); let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask) - && first_char.map_or(true, |ch| { - !(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch) - }); + && first_char + .is_none_or(|ch| !(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)); #[allow(non_upper_case_globals)] let key = match first_char { @@ -427,7 +426,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { key_char = Some(chars_for_modified_key(native_event.keyCode(), mods)); } - let mut key = if shift + if shift && chars_ignoring_modifiers .chars() .all(|c| c.is_ascii_lowercase()) @@ -438,9 +437,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { chars_with_shift } else { chars_ignoring_modifiers - }; - - key + } } }; diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index a686d8c45b..49a5edceb2 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -445,14 +445,14 @@ impl MetalRenderer { instance_buffer, &mut instance_offset, viewport_size, - &command_encoder, + command_encoder, ), PrimitiveBatch::Quads(quads) => self.draw_quads( quads, instance_buffer, &mut instance_offset, viewport_size, - &command_encoder, + command_encoder, ), PrimitiveBatch::Paths(paths) => { command_encoder.end_encoding(); @@ -480,7 +480,7 @@ impl MetalRenderer { instance_buffer, &mut instance_offset, viewport_size, - &command_encoder, + command_encoder, ) } else { false @@ -491,7 +491,7 @@ impl MetalRenderer { instance_buffer, &mut instance_offset, viewport_size, - &command_encoder, + command_encoder, ), PrimitiveBatch::MonochromeSprites { texture_id, @@ -502,7 +502,7 @@ impl MetalRenderer { instance_buffer, &mut instance_offset, viewport_size, - &command_encoder, + command_encoder, ), PrimitiveBatch::PolychromeSprites { texture_id, @@ -513,14 +513,14 @@ impl MetalRenderer { instance_buffer, &mut instance_offset, viewport_size, - &command_encoder, + command_encoder, ), PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces( surfaces, instance_buffer, &mut instance_offset, viewport_size, - &command_encoder, + command_encoder, ), }; if !ok { @@ -763,7 +763,7 @@ impl MetalRenderer { viewport_size: Size<DevicePixels>, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { - let Some(ref first_path) = paths.first() else { + let Some(first_path) = paths.first() else { return true; }; diff --git a/crates/gpui/src/platform/mac/open_type.rs b/crates/gpui/src/platform/mac/open_type.rs index 2ae5e8f87a..37a29559fd 100644 --- a/crates/gpui/src/platform/mac/open_type.rs +++ b/crates/gpui/src/platform/mac/open_type.rs @@ -35,14 +35,14 @@ pub fn apply_features_and_fallbacks( unsafe { let mut keys = vec![kCTFontFeatureSettingsAttribute]; let mut values = vec![generate_feature_array(features)]; - if let Some(fallbacks) = fallbacks { - if !fallbacks.fallback_list().is_empty() { - keys.push(kCTFontCascadeListAttribute); - values.push(generate_fallback_array( - fallbacks, - font.native_font().as_concrete_TypeRef(), - )); - } + if let Some(fallbacks) = fallbacks + && !fallbacks.fallback_list().is_empty() + { + keys.push(kCTFontCascadeListAttribute); + values.push(generate_fallback_array( + fallbacks, + font.native_font().as_concrete_TypeRef(), + )); } let attrs = CFDictionaryCreate( kCFAllocatorDefault, diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 533423229c..832550dc46 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -371,7 +371,7 @@ impl MacPlatform { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( - ns_string(&name), + ns_string(name), selector, ns_string(key_to_native(&keystroke.key).as_ref()), ) @@ -383,7 +383,7 @@ impl MacPlatform { } else { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( - ns_string(&name), + ns_string(name), selector, ns_string(""), ) @@ -392,7 +392,7 @@ impl MacPlatform { } else { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( - ns_string(&name), + ns_string(name), selector, ns_string(""), ) @@ -412,7 +412,7 @@ impl MacPlatform { submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap)); } item.setSubmenu_(submenu); - item.setTitle_(ns_string(&name)); + item.setTitle_(ns_string(name)); item } MenuItem::SystemMenu(OsMenu { name, menu_type }) => { @@ -420,7 +420,7 @@ impl MacPlatform { let submenu = NSMenu::new(nil).autorelease(); submenu.setDelegate_(delegate); item.setSubmenu_(submenu); - item.setTitle_(ns_string(&name)); + item.setTitle_(ns_string(name)); match menu_type { SystemMenuType::Services => { @@ -705,6 +705,7 @@ impl Platform for MacPlatform { panel.setCanChooseDirectories_(options.directories.to_objc()); panel.setCanChooseFiles_(options.files.to_objc()); panel.setAllowsMultipleSelection_(options.multiple.to_objc()); + panel.setCanCreateDirectories(true.to_objc()); panel.setResolvesAliases_(false.to_objc()); let done_tx = Cell::new(Some(done_tx)); @@ -714,10 +715,10 @@ impl Platform for MacPlatform { let urls = panel.URLs(); for i in 0..urls.count() { let url = urls.objectAtIndex(i); - if url.isFileURL() == YES { - if let Ok(path) = ns_url_to_path(url) { - result.push(path) - } + if url.isFileURL() == YES + && let Ok(path) = ns_url_to_path(url) + { + result.push(path) } } Some(result) @@ -730,6 +731,11 @@ impl Platform for MacPlatform { } }); let block = block.copy(); + + if let Some(prompt) = options.prompt { + let _: () = msg_send![panel, setPrompt: ns_string(&prompt)]; + } + let _: () = msg_send![panel, beginWithCompletionHandler: block]; } }) @@ -780,17 +786,18 @@ impl Platform for MacPlatform { // This is conditional on OS version because I'd like to get rid of it, so that // you can manually create a file called `a.sql.s`. That said it seems better // to break that use-case than breaking `a.sql`. - if chunks.len() == 3 && chunks[1].starts_with(chunks[2]) { - if Self::os_version() >= SemanticVersion::new(15, 0, 0) { - let new_filename = OsStr::from_bytes( - &filename.as_bytes() - [..chunks[0].len() + 1 + chunks[1].len()], - ) - .to_owned(); - result.set_file_name(&new_filename); - } + if chunks.len() == 3 + && chunks[1].starts_with(chunks[2]) + && Self::os_version() >= SemanticVersion::new(15, 0, 0) + { + let new_filename = OsStr::from_bytes( + &filename.as_bytes() + [..chunks[0].len() + 1 + chunks[1].len()], + ) + .to_owned(); + result.set_file_name(&new_filename); } - return result; + result }) } } diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 849925c727..72a0f2e565 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -319,7 +319,7 @@ impl MacTextSystemState { fn is_emoji(&self, font_id: FontId) -> bool { self.postscript_names_by_font_id .get(&font_id) - .map_or(false, |postscript_name| { + .is_some_and(|postscript_name| { postscript_name == "AppleColorEmoji" || postscript_name == ".AppleColorEmojiUI" }) } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index aedf131909..cd923a1859 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -653,7 +653,7 @@ impl MacWindow { .and_then(|titlebar| titlebar.traffic_light_position), transparent_titlebar: titlebar .as_ref() - .map_or(true, |titlebar| titlebar.appears_transparent), + .is_none_or(|titlebar| titlebar.appears_transparent), previous_modifiers_changed_event: None, keystroke_for_do_command: None, do_command_handled: None, @@ -688,7 +688,7 @@ impl MacWindow { }); } - if titlebar.map_or(true, |titlebar| titlebar.appears_transparent) { + if titlebar.is_none_or(|titlebar| titlebar.appears_transparent) { native_window.setTitlebarAppearsTransparent_(YES); native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden); } @@ -1478,18 +1478,18 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: return YES; } - if key_down_event.is_held { - if let Some(key_char) = key_down_event.keystroke.key_char.as_ref() { - let handled = with_input_handler(&this, |input_handler| { - if !input_handler.apple_press_and_hold_enabled() { - input_handler.replace_text_in_range(None, &key_char); - return YES; - } - NO - }); - if handled == Some(YES) { + if key_down_event.is_held + && let Some(key_char) = key_down_event.keystroke.key_char.as_ref() + { + let handled = with_input_handler(this, |input_handler| { + if !input_handler.apple_press_and_hold_enabled() { + input_handler.replace_text_in_range(None, key_char); return YES; } + NO + }); + if handled == Some(YES) { + return YES; } } @@ -1624,10 +1624,10 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { modifiers: prev_modifiers, capslock: prev_capslock, })) = &lock.previous_modifiers_changed_event + && prev_modifiers == modifiers + && prev_capslock == capslock { - if prev_modifiers == modifiers && prev_capslock == capslock { - return; - } + return; } lock.previous_modifiers_changed_event = Some(event.clone()); @@ -1949,7 +1949,7 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS let text = text.to_str(); let replacement_range = replacement_range.to_range(); with_input_handler(this, |input_handler| { - input_handler.replace_text_in_range(replacement_range, &text) + input_handler.replace_text_in_range(replacement_range, text) }); } } @@ -1973,7 +1973,7 @@ extern "C" fn set_marked_text( let replacement_range = replacement_range.to_range(); let text = text.to_str(); with_input_handler(this, |input_handler| { - input_handler.replace_and_mark_text_in_range(replacement_range, &text, selected_range) + input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range) }); } } @@ -1995,10 +1995,10 @@ extern "C" fn attributed_substring_for_proposed_range( let mut adjusted: Option<Range<usize>> = None; let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?; - if let Some(adjusted) = adjusted { - if adjusted != range { - unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) }; - } + if let Some(adjusted) = adjusted + && adjusted != range + { + unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) }; } unsafe { let string: id = msg_send![class!(NSAttributedString), alloc]; @@ -2063,8 +2063,8 @@ fn screen_point_to_gpui_point(this: &Object, position: NSPoint) -> Point<Pixels> let frame = get_frame(this); let window_x = position.x - frame.origin.x; let window_y = frame.size.height - (position.y - frame.origin.y); - let position = point(px(window_x as f32), px(window_y as f32)); - position + + point(px(window_x as f32), px(window_y as f32)) } extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation { @@ -2073,11 +2073,10 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr let paths = external_paths_from_event(dragging_info); if let Some(event) = paths.map(|paths| PlatformInput::FileDrop(FileDropEvent::Entered { position, paths })) + && send_new_event(&window_state, event) { - if send_new_event(&window_state, event) { - window_state.lock().external_files_dragged = true; - return NSDragOperationCopy; - } + window_state.lock().external_files_dragged = true; + return NSDragOperationCopy; } NSDragOperationNone } diff --git a/crates/gpui/src/platform/scap_screen_capture.rs b/crates/gpui/src/platform/scap_screen_capture.rs index 32041b655f..d6d19cd810 100644 --- a/crates/gpui/src/platform/scap_screen_capture.rs +++ b/crates/gpui/src/platform/scap_screen_capture.rs @@ -228,7 +228,7 @@ fn run_capture( display, size, })); - if let Err(_) = stream_send_result { + if stream_send_result.is_err() { return; } while !cancel_stream.load(std::sync::atomic::Ordering::SeqCst) { diff --git a/crates/gpui/src/platform/test/dispatcher.rs b/crates/gpui/src/platform/test/dispatcher.rs index 16edabfa4b..4ce62c4bdc 100644 --- a/crates/gpui/src/platform/test/dispatcher.rs +++ b/crates/gpui/src/platform/test/dispatcher.rs @@ -78,11 +78,11 @@ impl TestDispatcher { let state = self.state.lock(); let next_due_time = state.delayed.first().map(|(time, _)| *time); drop(state); - if let Some(due_time) = next_due_time { - if due_time <= new_now { - self.state.lock().time = due_time; - continue; - } + if let Some(due_time) = next_due_time + && due_time <= new_now + { + self.state.lock().time = due_time; + continue; } break; } @@ -270,9 +270,7 @@ impl PlatformDispatcher for TestDispatcher { fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) { { let mut state = self.state.lock(); - if label.map_or(false, |label| { - state.deprioritized_task_labels.contains(&label) - }) { + if label.is_some_and(|label| state.deprioritized_task_labels.contains(&label)) { state.deprioritized_background.push(runnable); } else { state.background.push(runnable); diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 69371bc8c4..2b4914baed 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -201,10 +201,10 @@ impl TestPlatform { executor .spawn(async move { if let Some(previous_window) = previous_window { - if let Some(window) = window.as_ref() { - if Rc::ptr_eq(&previous_window.0, &window.0) { - return; - } + if let Some(window) = window.as_ref() + && Rc::ptr_eq(&previous_window.0, &window.0) + { + return; } previous_window.simulate_active_status_change(false); } diff --git a/crates/gpui/src/platform/windows/direct_write.rs b/crates/gpui/src/platform/windows/direct_write.rs index 75cb50243b..a86a1fab62 100644 --- a/crates/gpui/src/platform/windows/direct_write.rs +++ b/crates/gpui/src/platform/windows/direct_write.rs @@ -850,7 +850,7 @@ impl DirectWriteState { } let bitmap_data = if params.is_emoji { - if let Ok(color) = self.rasterize_color(¶ms, glyph_bounds) { + if let Ok(color) = self.rasterize_color(params, glyph_bounds) { color } else { let monochrome = self.rasterize_monochrome(params, glyph_bounds)?; @@ -1784,7 +1784,7 @@ fn apply_font_features( } unsafe { - direct_write_features.AddFontFeature(make_direct_write_feature(&tag, *value))?; + direct_write_features.AddFontFeature(make_direct_write_feature(tag, *value))?; } } unsafe { diff --git a/crates/gpui/src/platform/windows/directx_renderer.rs b/crates/gpui/src/platform/windows/directx_renderer.rs index 4e72ded534..f84a1c1b6d 100644 --- a/crates/gpui/src/platform/windows/directx_renderer.rs +++ b/crates/gpui/src/platform/windows/directx_renderer.rs @@ -758,7 +758,7 @@ impl DirectXRenderPipelines { impl DirectComposition { pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result<Self> { - let comp_device = get_comp_device(&dxgi_device)?; + let comp_device = get_comp_device(dxgi_device)?; let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?; let comp_visual = unsafe { comp_device.CreateVisual() }?; @@ -1144,7 +1144,7 @@ fn create_resources( [D3D11_VIEWPORT; 1], )> { let (render_target, render_target_view) = - create_render_target_and_its_view(&swap_chain, &devices.device)?; + create_render_target_and_its_view(swap_chain, &devices.device)?; let (path_intermediate_texture, path_intermediate_srv) = create_path_intermediate_texture(&devices.device, width, height)?; let (path_intermediate_msaa_texture, path_intermediate_msaa_view) = diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index 4ab257d27a..607163b577 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -100,6 +100,7 @@ impl WindowsWindowInner { WM_SETCURSOR => self.handle_set_cursor(handle, lparam), WM_SETTINGCHANGE => self.handle_system_settings_changed(handle, wparam, lparam), WM_INPUTLANGCHANGE => self.handle_input_language_changed(lparam), + WM_SHOWWINDOW => self.handle_window_visibility_changed(handle, wparam), WM_GPUI_CURSOR_STYLE_CHANGED => self.handle_cursor_changed(lparam), WM_GPUI_FORCE_UPDATE_WINDOW => self.draw_window(handle, true), _ => None, @@ -700,29 +701,28 @@ impl WindowsWindowInner { // Fix auto hide taskbar not showing. This solution is based on the approach // used by Chrome. However, it may result in one row of pixels being obscured // in our client area. But as Chrome says, "there seems to be no better solution." - if is_maximized { - if let Some(ref taskbar_position) = self + if is_maximized + && let Some(ref taskbar_position) = self .state .borrow() .system_settings .auto_hide_taskbar_position - { - // Fot the auto-hide taskbar, adjust in by 1 pixel on taskbar edge, - // so the window isn't treated as a "fullscreen app", which would cause - // the taskbar to disappear. - match taskbar_position { - AutoHideTaskbarPosition::Left => { - requested_client_rect[0].left += AUTO_HIDE_TASKBAR_THICKNESS_PX - } - AutoHideTaskbarPosition::Top => { - requested_client_rect[0].top += AUTO_HIDE_TASKBAR_THICKNESS_PX - } - AutoHideTaskbarPosition::Right => { - requested_client_rect[0].right -= AUTO_HIDE_TASKBAR_THICKNESS_PX - } - AutoHideTaskbarPosition::Bottom => { - requested_client_rect[0].bottom -= AUTO_HIDE_TASKBAR_THICKNESS_PX - } + { + // Fot the auto-hide taskbar, adjust in by 1 pixel on taskbar edge, + // so the window isn't treated as a "fullscreen app", which would cause + // the taskbar to disappear. + match taskbar_position { + AutoHideTaskbarPosition::Left => { + requested_client_rect[0].left += AUTO_HIDE_TASKBAR_THICKNESS_PX + } + AutoHideTaskbarPosition::Top => { + requested_client_rect[0].top += AUTO_HIDE_TASKBAR_THICKNESS_PX + } + AutoHideTaskbarPosition::Right => { + requested_client_rect[0].right -= AUTO_HIDE_TASKBAR_THICKNESS_PX + } + AutoHideTaskbarPosition::Bottom => { + requested_client_rect[0].bottom -= AUTO_HIDE_TASKBAR_THICKNESS_PX } } } @@ -1124,27 +1124,22 @@ impl WindowsWindowInner { // lParam is a pointer to a string that indicates the area containing the system parameter // that was changed. let parameter = PCWSTR::from_raw(lparam.0 as _); - if unsafe { !parameter.is_null() && !parameter.is_empty() } { - if let Some(parameter_string) = unsafe { parameter.to_string() }.log_err() { - log::info!("System settings changed: {}", parameter_string); - match parameter_string.as_str() { - "ImmersiveColorSet" => { - let new_appearance = system_appearance() - .context( - "unable to get system appearance when handling ImmersiveColorSet", - ) - .log_err()?; - let mut lock = self.state.borrow_mut(); - if new_appearance != lock.appearance { - lock.appearance = new_appearance; - let mut callback = lock.callbacks.appearance_changed.take()?; - drop(lock); - callback(); - self.state.borrow_mut().callbacks.appearance_changed = Some(callback); - configure_dwm_dark_mode(handle, new_appearance); - } - } - _ => {} + if unsafe { !parameter.is_null() && !parameter.is_empty() } + && let Some(parameter_string) = unsafe { parameter.to_string() }.log_err() + { + log::info!("System settings changed: {}", parameter_string); + if parameter_string.as_str() == "ImmersiveColorSet" { + let new_appearance = system_appearance() + .context("unable to get system appearance when handling ImmersiveColorSet") + .log_err()?; + let mut lock = self.state.borrow_mut(); + if new_appearance != lock.appearance { + lock.appearance = new_appearance; + let mut callback = lock.callbacks.appearance_changed.take()?; + drop(lock); + callback(); + self.state.borrow_mut().callbacks.appearance_changed = Some(callback); + configure_dwm_dark_mode(handle, new_appearance); } } } @@ -1160,6 +1155,13 @@ impl WindowsWindowInner { Some(0) } + fn handle_window_visibility_changed(&self, handle: HWND, wparam: WPARAM) -> Option<isize> { + if wparam.0 == 1 { + self.draw_window(handle, false); + } + None + } + fn handle_device_change_msg(&self, handle: HWND, wparam: WPARAM) -> Option<isize> { if wparam.0 == DBT_DEVNODES_CHANGED as usize { // The reason for sending this message is to actually trigger a redraw of the window. @@ -1464,7 +1466,7 @@ pub(crate) fn current_modifiers() -> Modifiers { #[inline] pub(crate) fn current_capslock() -> Capslock { let on = unsafe { GetKeyState(VK_CAPITAL.0 as i32) & 1 } > 0; - Capslock { on: on } + Capslock { on } } fn get_client_area_insets( diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index c1fb0cabc4..b13b9915f1 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -227,7 +227,7 @@ impl WindowsPlatform { | WM_GPUI_CLOSE_ONE_WINDOW | WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD | WM_GPUI_DOCK_MENU_ACTION => { - if self.handle_gpui_evnets(msg.message, msg.wParam, msg.lParam, &msg) { + if self.handle_gpui_events(msg.message, msg.wParam, msg.lParam, &msg) { return; } } @@ -240,7 +240,7 @@ impl WindowsPlatform { } // Returns true if the app should quit. - fn handle_gpui_evnets( + fn handle_gpui_events( &self, message: u32, wparam: WPARAM, @@ -787,6 +787,12 @@ fn file_open_dialog( unsafe { folder_dialog.SetOptions(dialog_options)?; + + if let Some(prompt) = options.prompt { + let prompt: &str = &prompt; + folder_dialog.SetOkButtonLabel(&HSTRING::from(prompt))?; + } + if folder_dialog.Show(window).is_err() { // User cancelled return Ok(None); @@ -815,14 +821,14 @@ fn file_save_dialog( window: Option<HWND>, ) -> Result<Option<PathBuf>> { let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? }; - if !directory.to_string_lossy().is_empty() { - if let Some(full_path) = directory.canonicalize().log_err() { - let full_path = SanitizedPath::from(full_path); - let full_path_string = full_path.to_string(); - let path_item: IShellItem = - unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? }; - unsafe { dialog.SetFolder(&path_item).log_err() }; - } + if !directory.to_string_lossy().is_empty() + && let Some(full_path) = directory.canonicalize().log_err() + { + let full_path = SanitizedPath::from(full_path); + let full_path_string = full_path.to_string(); + let path_item: IShellItem = + unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? }; + unsafe { dialog.SetFolder(&path_item).log_err() }; } if let Some(suggested_name) = suggested_name { diff --git a/crates/gpui/src/shared_string.rs b/crates/gpui/src/shared_string.rs index c325f98cd2..a34b7502f0 100644 --- a/crates/gpui/src/shared_string.rs +++ b/crates/gpui/src/shared_string.rs @@ -23,6 +23,11 @@ impl SharedString { pub fn new(str: impl Into<Arc<str>>) -> Self { SharedString(ArcCow::Owned(str.into())) } + + /// Get a &str from the underlying string. + pub fn as_str(&self) -> &str { + &self.0 + } } impl JsonSchema for SharedString { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 5e3630e1f9..e5cff552ad 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -573,7 +573,7 @@ impl Style { if self .border_color - .map_or(false, |color| !color.is_transparent()) + .is_some_and(|color| !color.is_transparent()) { min.x += self.border_widths.left.to_pixels(rem_size); max.x -= self.border_widths.right.to_pixels(rem_size); @@ -633,7 +633,7 @@ impl Style { window.paint_shadows(bounds, corner_radii, &self.box_shadow); let background_color = self.background.as_ref().and_then(Fill::color); - if background_color.map_or(false, |color| !color.is_transparent()) { + if background_color.is_some_and(|color| !color.is_transparent()) { let mut border_color = match background_color { Some(color) => match color.tag { BackgroundTag::Solid => color.solid, @@ -729,7 +729,7 @@ impl Style { fn is_border_visible(&self) -> bool { self.border_color - .map_or(false, |color| !color.is_transparent()) + .is_some_and(|color| !color.is_transparent()) && self.border_widths.any(|length| !length.is_zero()) } } diff --git a/crates/gpui/src/subscription.rs b/crates/gpui/src/subscription.rs index a584f1a45f..bd869f8d32 100644 --- a/crates/gpui/src/subscription.rs +++ b/crates/gpui/src/subscription.rs @@ -201,3 +201,9 @@ impl Drop for Subscription { } } } + +impl std::fmt::Debug for Subscription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Subscription").finish() + } +} diff --git a/crates/gpui/src/tab_stop.rs b/crates/gpui/src/tab_stop.rs index 7dde42efed..30d24e85e7 100644 --- a/crates/gpui/src/tab_stop.rs +++ b/crates/gpui/src/tab_stop.rs @@ -90,7 +90,7 @@ mod tests { ]; for handle in focus_handles.iter() { - tab.insert(&handle); + tab.insert(handle); } assert_eq!( tab.handles diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 9e6ae0f22f..801d6b2567 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -3,6 +3,7 @@ use crate::{ }; use collections::{FxHashMap, FxHashSet}; use smallvec::SmallVec; +use stacksafe::{StackSafe, stacksafe}; use std::{fmt::Debug, ops::Range}; use taffy::{ TaffyTree, TraversePartialTree as _, @@ -11,8 +12,15 @@ use taffy::{ tree::NodeId, }; -type NodeMeasureFn = Box< - dyn FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut Window, &mut App) -> Size<Pixels>, +type NodeMeasureFn = StackSafe< + Box< + dyn FnMut( + Size<Option<Pixels>>, + Size<AvailableSpace>, + &mut Window, + &mut App, + ) -> Size<Pixels>, + >, >; struct NodeContext { @@ -50,23 +58,21 @@ impl TaffyLayoutEngine { children: &[LayoutId], ) -> LayoutId { let taffy_style = style.to_taffy(rem_size); - let layout_id = if children.is_empty() { + + if children.is_empty() { self.taffy .new_leaf(taffy_style) .expect(EXPECT_MESSAGE) .into() } else { - let parent_id = self - .taffy + self.taffy // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId. .new_with_children(taffy_style, unsafe { std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(children) }) .expect(EXPECT_MESSAGE) - .into(); - parent_id - }; - layout_id + .into() + } } pub fn request_measured_layout( @@ -83,17 +89,15 @@ impl TaffyLayoutEngine { ) -> LayoutId { let taffy_style = style.to_taffy(rem_size); - let layout_id = self - .taffy + self.taffy .new_leaf_with_context( taffy_style, NodeContext { - measure: Box::new(measure), + measure: StackSafe::new(Box::new(measure)), }, ) .expect(EXPECT_MESSAGE) - .into(); - layout_id + .into() } // Used to understand performance @@ -143,6 +147,7 @@ impl TaffyLayoutEngine { Ok(edges) } + #[stacksafe] pub fn compute_layout( &mut self, id: LayoutId, diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index b48c3a2935..53991089da 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -366,15 +366,14 @@ impl WindowTextSystem { let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new(); for run in runs { - if let Some(last_run) = decoration_runs.last_mut() { - if last_run.color == run.color - && last_run.underline == run.underline - && last_run.strikethrough == run.strikethrough - && last_run.background_color == run.background_color - { - last_run.len += run.len as u32; - continue; - } + if let Some(last_run) = decoration_runs.last_mut() + && last_run.color == run.color + && last_run.underline == run.underline + && last_run.strikethrough == run.strikethrough + && last_run.background_color == run.background_color + { + last_run.len += run.len as u32; + continue; } decoration_runs.push(DecorationRun { len: run.len as u32, @@ -436,7 +435,7 @@ impl WindowTextSystem { }); } - if decoration_runs.last().map_or(false, |last_run| { + if decoration_runs.last().is_some_and(|last_run| { last_run.color == run.color && last_run.underline == run.underline && last_run.strikethrough == run.strikethrough @@ -492,14 +491,14 @@ impl WindowTextSystem { let mut split_lines = text.split('\n'); let mut processed = false; - if let Some(first_line) = split_lines.next() { - if let Some(second_line) = split_lines.next() { - processed = true; - process_line(first_line.to_string().into()); - process_line(second_line.to_string().into()); - for line_text in split_lines { - process_line(line_text.to_string().into()); - } + if let Some(first_line) = split_lines.next() + && let Some(second_line) = split_lines.next() + { + processed = true; + process_line(first_line.to_string().into()); + process_line(second_line.to_string().into()); + for line_text in split_lines { + process_line(line_text.to_string().into()); } } @@ -534,11 +533,11 @@ impl WindowTextSystem { let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); for run in runs.iter() { let font_id = self.resolve_font(&run.font); - if let Some(last_run) = font_runs.last_mut() { - if last_run.font_id == font_id { - last_run.len += run.len; - continue; - } + if let Some(last_run) = font_runs.last_mut() + && last_run.font_id == font_id + { + last_run.len += run.len; + continue; } font_runs.push(FontRun { len: run.len, diff --git a/crates/gpui/src/text_system/line.rs b/crates/gpui/src/text_system/line.rs index 3813393d81..8d559f9815 100644 --- a/crates/gpui/src/text_system/line.rs +++ b/crates/gpui/src/text_system/line.rs @@ -292,10 +292,10 @@ fn paint_line( } if let Some(style_run) = style_run { - if let Some((_, underline_style)) = &mut current_underline { - if style_run.underline.as_ref() != Some(underline_style) { - finished_underline = current_underline.take(); - } + if let Some((_, underline_style)) = &mut current_underline + && style_run.underline.as_ref() != Some(underline_style) + { + finished_underline = current_underline.take(); } if let Some(run_underline) = style_run.underline.as_ref() { current_underline.get_or_insert(( @@ -310,10 +310,10 @@ fn paint_line( }, )); } - if let Some((_, strikethrough_style)) = &mut current_strikethrough { - if style_run.strikethrough.as_ref() != Some(strikethrough_style) { - finished_strikethrough = current_strikethrough.take(); - } + if let Some((_, strikethrough_style)) = &mut current_strikethrough + && style_run.strikethrough.as_ref() != Some(strikethrough_style) + { + finished_strikethrough = current_strikethrough.take(); } if let Some(run_strikethrough) = style_run.strikethrough.as_ref() { current_strikethrough.get_or_insert(( @@ -509,10 +509,10 @@ fn paint_line_background( } if let Some(style_run) = style_run { - if let Some((_, background_color)) = &mut current_background { - if style_run.background_color.as_ref() != Some(background_color) { - finished_background = current_background.take(); - } + if let Some((_, background_color)) = &mut current_background + && style_run.background_color.as_ref() != Some(background_color) + { + finished_background = current_background.take(); } if let Some(run_background) = style_run.background_color { current_background.get_or_insert(( diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 9c2dd7f087..43694702a8 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -185,10 +185,10 @@ impl LineLayout { if width > wrap_width && boundary > last_boundary { // When used line_clamp, we should limit the number of lines. - if let Some(max_lines) = max_lines { - if boundaries.len() >= max_lines - 1 { - break; - } + if let Some(max_lines) = max_lines + && boundaries.len() >= max_lines - 1 + { + break; } if let Some(last_candidate_ix) = last_candidate_ix.take() { diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index 648d714c89..93ec6c854c 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -44,7 +44,7 @@ impl LineWrapper { let mut prev_c = '\0'; let mut index = 0; let mut candidates = fragments - .into_iter() + .iter() .flat_map(move |fragment| fragment.wrap_boundary_candidates()) .peekable(); iter::from_fn(move || { diff --git a/crates/gpui/src/util.rs b/crates/gpui/src/util.rs index f357034fbf..3d7fa06e6c 100644 --- a/crates/gpui/src/util.rs +++ b/crates/gpui/src/util.rs @@ -58,13 +58,7 @@ pub trait FluentBuilder { where Self: Sized, { - self.map(|this| { - if let Some(_) = option { - this - } else { - then(this) - } - }) + self.map(|this| if option.is_some() { this } else { then(this) }) } } diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index f461e2f7d0..217971792e 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -205,22 +205,21 @@ impl Element for AnyView { let content_mask = window.content_mask(); let text_style = window.text_style(); - if let Some(mut element_state) = element_state { - if element_state.cache_key.bounds == bounds - && element_state.cache_key.content_mask == content_mask - && element_state.cache_key.text_style == text_style - && !window.dirty_views.contains(&self.entity_id()) - && !window.refreshing - { - let prepaint_start = window.prepaint_index(); - window.reuse_prepaint(element_state.prepaint_range.clone()); - cx.entities - .extend_accessed(&element_state.accessed_entities); - let prepaint_end = window.prepaint_index(); - element_state.prepaint_range = prepaint_start..prepaint_end; + if let Some(mut element_state) = element_state + && element_state.cache_key.bounds == bounds + && element_state.cache_key.content_mask == content_mask + && element_state.cache_key.text_style == text_style + && !window.dirty_views.contains(&self.entity_id()) + && !window.refreshing + { + let prepaint_start = window.prepaint_index(); + window.reuse_prepaint(element_state.prepaint_range.clone()); + cx.entities + .extend_accessed(&element_state.accessed_entities); + let prepaint_end = window.prepaint_index(); + element_state.prepaint_range = prepaint_start..prepaint_end; - return (None, element_state); - } + return (None, element_state); } let refreshing = mem::replace(&mut window.refreshing, true); diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 0d7e3d3e0c..c4501daa59 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -243,14 +243,14 @@ impl FocusId { pub fn contains_focused(&self, window: &Window, cx: &App) -> bool { window .focused(cx) - .map_or(false, |focused| self.contains(focused.id, window)) + .is_some_and(|focused| self.contains(focused.id, window)) } /// Obtains whether the element associated with this handle is contained within the /// focused element or is itself focused. pub fn within_focused(&self, window: &Window, cx: &App) -> bool { let focused = window.focused(cx); - focused.map_or(false, |focused| focused.id.contains(*self, window)) + focused.is_some_and(|focused| focused.id.contains(*self, window)) } /// Obtains whether this handle contains the given handle in the most recently rendered frame. @@ -504,7 +504,7 @@ impl HitboxId { return true; } } - return false; + false } /// Checks if the hitbox with this ID contains the mouse and should handle scroll events. @@ -634,7 +634,7 @@ impl TooltipId { window .tooltip_bounds .as_ref() - .map_or(false, |tooltip_bounds| { + .is_some_and(|tooltip_bounds| { tooltip_bounds.id == *self && tooltip_bounds.bounds.contains(&window.mouse_position()) }) @@ -3401,16 +3401,16 @@ impl Window { let focus_id = handle.id; let (subscription, activate) = self.new_focus_listener(Box::new(move |event, window, cx| { - if let Some(blurred_id) = event.previous_focus_path.last().copied() { - if event.is_focus_out(focus_id) { - let event = FocusOutEvent { - blurred: WeakFocusHandle { - id: blurred_id, - handles: Arc::downgrade(&cx.focus_handles), - }, - }; - listener(event, window, cx) - } + if let Some(blurred_id) = event.previous_focus_path.last().copied() + && event.is_focus_out(focus_id) + { + let event = FocusOutEvent { + blurred: WeakFocusHandle { + id: blurred_id, + handles: Arc::downgrade(&cx.focus_handles), + }, + }; + listener(event, window, cx) } true })); @@ -3444,12 +3444,12 @@ impl Window { return true; } - if let Some(input) = keystroke.key_char { - if let Some(mut input_handler) = self.platform_window.take_input_handler() { - input_handler.dispatch_input(&input, self, cx); - self.platform_window.set_input_handler(input_handler); - return true; - } + if let Some(input) = keystroke.key_char + && let Some(mut input_handler) = self.platform_window.take_input_handler() + { + input_handler.dispatch_input(&input, self, cx); + self.platform_window.set_input_handler(input_handler); + return true; } false @@ -3864,11 +3864,11 @@ impl Window { if !cx.propagate_event { continue 'replay; } - if let Some(input) = replay.keystroke.key_char.as_ref().cloned() { - if let Some(mut input_handler) = self.platform_window.take_input_handler() { - input_handler.dispatch_input(&input, self, cx); - self.platform_window.set_input_handler(input_handler) - } + if let Some(input) = replay.keystroke.key_char.as_ref().cloned() + && let Some(mut input_handler) = self.platform_window.take_input_handler() + { + input_handler.dispatch_input(&input, self, cx); + self.platform_window.set_input_handler(input_handler) } } } @@ -4309,15 +4309,15 @@ impl Window { cx: &mut App, f: impl FnOnce(&mut Option<T>, &mut Self) -> R, ) -> R { - if let Some(inspector_id) = _inspector_id { - if let Some(inspector) = &self.inspector { - let inspector = inspector.clone(); - let active_element_id = inspector.read(cx).active_element_id(); - if Some(inspector_id) == active_element_id { - return inspector.update(cx, |inspector, _cx| { - inspector.with_active_element_state(self, f) - }); - } + if let Some(inspector_id) = _inspector_id + && let Some(inspector) = &self.inspector + { + let inspector = inspector.clone(); + let active_element_id = inspector.read(cx).active_element_id(); + if Some(inspector_id) == active_element_id { + return inspector.update(cx, |inspector, _cx| { + inspector.with_active_element_state(self, f) + }); } } f(&mut None, self) @@ -4389,15 +4389,13 @@ impl Window { if let Some(inspector) = self.inspector.as_ref() { let inspector = inspector.read(cx); if let Some((hitbox_id, _)) = self.hovered_inspector_hitbox(inspector, &self.next_frame) - { - if let Some(hitbox) = self + && let Some(hitbox) = self .next_frame .hitboxes .iter() .find(|hitbox| hitbox.id == hitbox_id) - { - self.paint_quad(crate::fill(hitbox.bounds, crate::rgba(0x61afef4d))); - } + { + self.paint_quad(crate::fill(hitbox.bounds, crate::rgba(0x61afef4d))); } } } @@ -4468,7 +4466,7 @@ impl Window { } } } - return None; + None } } diff --git a/crates/gpui_macros/src/derive_inspector_reflection.rs b/crates/gpui_macros/src/derive_inspector_reflection.rs index fa22f95f9a..9c1cb503a8 100644 --- a/crates/gpui_macros/src/derive_inspector_reflection.rs +++ b/crates/gpui_macros/src/derive_inspector_reflection.rs @@ -160,16 +160,14 @@ fn extract_doc_comment(attrs: &[Attribute]) -> Option<String> { let mut doc_lines = Vec::new(); for attr in attrs { - if attr.path().is_ident("doc") { - if let Meta::NameValue(meta) = &attr.meta { - if let Expr::Lit(expr_lit) = &meta.value { - if let Lit::Str(lit_str) = &expr_lit.lit { - let line = lit_str.value(); - let line = line.strip_prefix(' ').unwrap_or(&line); - doc_lines.push(line.to_string()); - } - } - } + if attr.path().is_ident("doc") + && let Meta::NameValue(meta) = &attr.meta + && let Expr::Lit(expr_lit) = &meta.value + && let Lit::Str(lit_str) = &expr_lit.lit + { + let line = lit_str.value(); + let line = line.strip_prefix(' ').unwrap_or(&line); + doc_lines.push(line.to_string()); } } @@ -191,7 +189,7 @@ fn extract_cfg_attributes(attrs: &[Attribute]) -> Vec<Attribute> { fn is_called_from_gpui_crate(_span: Span) -> bool { // Check if we're being called from within the gpui crate by examining the call site // This is a heuristic approach - we check if the current crate name is "gpui" - std::env::var("CARGO_PKG_NAME").map_or(false, |name| name == "gpui") + std::env::var("CARGO_PKG_NAME").is_ok_and(|name| name == "gpui") } struct MacroExpander; diff --git a/crates/gpui_macros/src/test.rs b/crates/gpui_macros/src/test.rs index adb27f42ea..648d3499ed 100644 --- a/crates/gpui_macros/src/test.rs +++ b/crates/gpui_macros/src/test.rs @@ -73,7 +73,7 @@ impl Parse for Args { (Meta::NameValue(meta), "seed") => { seeds = vec![parse_usize_from_expr(&meta.value)? as u64] } - (Meta::List(list), "seeds") => seeds = parse_u64_array(&list)?, + (Meta::List(list), "seeds") => seeds = parse_u64_array(list)?, (Meta::Path(_), _) => { return Err(syn::Error::new(meta.span(), "invalid path argument")); } @@ -86,7 +86,7 @@ impl Parse for Args { Ok(Args { seeds, max_retries, - max_iterations: max_iterations, + max_iterations, on_failure_fn_name, }) } @@ -152,28 +152,28 @@ fn generate_test_function( } _ => {} } - } else if let Type::Reference(ty) = &*arg.ty { - if let Type::Path(ty) = &*ty.elem { - let last_segment = ty.path.segments.last(); - if let Some("TestAppContext") = - last_segment.map(|s| s.ident.to_string()).as_deref() - { - let cx_varname = format_ident!("cx_{}", ix); - cx_vars.extend(quote!( - let mut #cx_varname = gpui::TestAppContext::build( - dispatcher.clone(), - Some(stringify!(#outer_fn_name)), - ); - )); - cx_teardowns.extend(quote!( - dispatcher.run_until_parked(); - #cx_varname.executor().forbid_parking(); - #cx_varname.quit(); - dispatcher.run_until_parked(); - )); - inner_fn_args.extend(quote!(&mut #cx_varname,)); - continue; - } + } else if let Type::Reference(ty) = &*arg.ty + && let Type::Path(ty) = &*ty.elem + { + let last_segment = ty.path.segments.last(); + if let Some("TestAppContext") = + last_segment.map(|s| s.ident.to_string()).as_deref() + { + let cx_varname = format_ident!("cx_{}", ix); + cx_vars.extend(quote!( + let mut #cx_varname = gpui::TestAppContext::build( + dispatcher.clone(), + Some(stringify!(#outer_fn_name)), + ); + )); + cx_teardowns.extend(quote!( + dispatcher.run_until_parked(); + #cx_varname.executor().forbid_parking(); + #cx_varname.quit(); + dispatcher.run_until_parked(); + )); + inner_fn_args.extend(quote!(&mut #cx_varname,)); + continue; } } } @@ -215,48 +215,48 @@ fn generate_test_function( inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(_seed),)); continue; } - } else if let Type::Reference(ty) = &*arg.ty { - if let Type::Path(ty) = &*ty.elem { - let last_segment = ty.path.segments.last(); - match last_segment.map(|s| s.ident.to_string()).as_deref() { - Some("App") => { - let cx_varname = format_ident!("cx_{}", ix); - let cx_varname_lock = format_ident!("cx_{}_lock", ix); - cx_vars.extend(quote!( - let mut #cx_varname = gpui::TestAppContext::build( - dispatcher.clone(), - Some(stringify!(#outer_fn_name)) - ); - let mut #cx_varname_lock = #cx_varname.app.borrow_mut(); - )); - inner_fn_args.extend(quote!(&mut #cx_varname_lock,)); - cx_teardowns.extend(quote!( + } else if let Type::Reference(ty) = &*arg.ty + && let Type::Path(ty) = &*ty.elem + { + let last_segment = ty.path.segments.last(); + match last_segment.map(|s| s.ident.to_string()).as_deref() { + Some("App") => { + let cx_varname = format_ident!("cx_{}", ix); + let cx_varname_lock = format_ident!("cx_{}_lock", ix); + cx_vars.extend(quote!( + let mut #cx_varname = gpui::TestAppContext::build( + dispatcher.clone(), + Some(stringify!(#outer_fn_name)) + ); + let mut #cx_varname_lock = #cx_varname.app.borrow_mut(); + )); + inner_fn_args.extend(quote!(&mut #cx_varname_lock,)); + cx_teardowns.extend(quote!( drop(#cx_varname_lock); dispatcher.run_until_parked(); #cx_varname.update(|cx| { cx.background_executor().forbid_parking(); cx.quit(); }); dispatcher.run_until_parked(); )); - continue; - } - Some("TestAppContext") => { - let cx_varname = format_ident!("cx_{}", ix); - cx_vars.extend(quote!( - let mut #cx_varname = gpui::TestAppContext::build( - dispatcher.clone(), - Some(stringify!(#outer_fn_name)) - ); - )); - cx_teardowns.extend(quote!( - dispatcher.run_until_parked(); - #cx_varname.executor().forbid_parking(); - #cx_varname.quit(); - dispatcher.run_until_parked(); - )); - inner_fn_args.extend(quote!(&mut #cx_varname,)); - continue; - } - _ => {} + continue; } + Some("TestAppContext") => { + let cx_varname = format_ident!("cx_{}", ix); + cx_vars.extend(quote!( + let mut #cx_varname = gpui::TestAppContext::build( + dispatcher.clone(), + Some(stringify!(#outer_fn_name)) + ); + )); + cx_teardowns.extend(quote!( + dispatcher.run_until_parked(); + #cx_varname.executor().forbid_parking(); + #cx_varname.quit(); + dispatcher.run_until_parked(); + )); + inner_fn_args.extend(quote!(&mut #cx_varname,)); + continue; + } + _ => {} } } } diff --git a/crates/html_to_markdown/src/markdown.rs b/crates/html_to_markdown/src/markdown.rs index b9ffbac79c..bb3b3563bc 100644 --- a/crates/html_to_markdown/src/markdown.rs +++ b/crates/html_to_markdown/src/markdown.rs @@ -34,15 +34,14 @@ impl HandleTag for ParagraphHandler { tag: &HtmlElement, writer: &mut MarkdownWriter, ) -> StartTagOutcome { - if tag.is_inline() && writer.is_inside("p") { - if let Some(parent) = writer.current_element_stack().iter().last() { - if !(parent.is_inline() - || writer.markdown.ends_with(' ') - || writer.markdown.ends_with('\n')) - { - writer.push_str(" "); - } - } + if tag.is_inline() + && writer.is_inside("p") + && let Some(parent) = writer.current_element_stack().iter().last() + && !(parent.is_inline() + || writer.markdown.ends_with(' ') + || writer.markdown.ends_with('\n')) + { + writer.push_str(" "); } if tag.tag() == "p" { diff --git a/crates/http_client/src/github.rs b/crates/http_client/src/github.rs index 89309ff344..32efed8e72 100644 --- a/crates/http_client/src/github.rs +++ b/crates/http_client/src/github.rs @@ -77,10 +77,10 @@ pub async fn latest_github_release( .find(|release| release.pre_release == pre_release) .context("finding a prerelease")?; release.assets.iter_mut().for_each(|asset| { - if let Some(digest) = &mut asset.digest { - if let Some(stripped) = digest.strip_prefix("sha256:") { - *digest = stripped.to_owned(); - } + if let Some(digest) = &mut asset.digest + && let Some(stripped) = digest.strip_prefix("sha256:") + { + *digest = stripped.to_owned(); } }); Ok(release) diff --git a/crates/http_client/src/http_client.rs b/crates/http_client/src/http_client.rs index a7f75b0962..62468573ed 100644 --- a/crates/http_client/src/http_client.rs +++ b/crates/http_client/src/http_client.rs @@ -435,8 +435,7 @@ impl HttpClient for FakeHttpClient { &self, req: Request<AsyncBody>, ) -> BoxFuture<'static, anyhow::Result<Response<AsyncBody>>> { - let future = (self.handler.lock().as_ref().unwrap())(req); - future + ((self.handler.lock().as_ref().unwrap())(req)) as _ } fn user_agent(&self) -> Option<&HeaderValue> { diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index 8bd76cbecf..38f02c2206 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -155,6 +155,7 @@ pub enum IconName { Maximize, Menu, MenuAlt, + MenuAltTemp, Mic, MicMute, Minimize, @@ -245,6 +246,8 @@ pub enum IconName { Warning, WholeWord, XCircle, + XCircleFilled, + ZedAgent, ZedAssistant, ZedBurnMode, ZedBurnModeOn, diff --git a/crates/inspector_ui/src/div_inspector.rs b/crates/inspector_ui/src/div_inspector.rs index bd395aa01b..0c2b16b9f4 100644 --- a/crates/inspector_ui/src/div_inspector.rs +++ b/crates/inspector_ui/src/div_inspector.rs @@ -93,8 +93,8 @@ impl DivInspector { Ok((json_style_buffer, rust_style_buffer)) => { this.update_in(cx, |this, window, cx| { this.state = State::BuffersLoaded { - json_style_buffer: json_style_buffer, - rust_style_buffer: rust_style_buffer, + json_style_buffer, + rust_style_buffer, }; // Initialize editors immediately instead of waiting for @@ -200,8 +200,8 @@ impl DivInspector { cx.subscribe_in(&json_style_editor, window, { let id = id.clone(); let rust_style_buffer = rust_style_buffer.clone(); - move |this, editor, event: &EditorEvent, window, cx| match event { - EditorEvent::BufferEdited => { + move |this, editor, event: &EditorEvent, window, cx| { + if event == &EditorEvent::BufferEdited { let style_json = editor.read(cx).text(cx); match serde_json_lenient::from_str_lenient::<StyleRefinement>(&style_json) { Ok(new_style) => { @@ -243,7 +243,6 @@ impl DivInspector { Err(err) => this.json_style_error = Some(err.to_string().into()), } } - _ => {} } }) .detach(); @@ -251,11 +250,10 @@ impl DivInspector { cx.subscribe(&rust_style_editor, { let json_style_buffer = json_style_buffer.clone(); let rust_style_buffer = rust_style_buffer.clone(); - move |this, _editor, event: &EditorEvent, cx| match event { - EditorEvent::BufferEdited => { + move |this, _editor, event: &EditorEvent, cx| { + if let EditorEvent::BufferEdited = event { this.update_json_style_from_rust(&json_style_buffer, &rust_style_buffer, cx); } - _ => {} } }) .detach(); @@ -271,23 +269,19 @@ impl DivInspector { } fn reset_style(&mut self, cx: &mut App) { - match &self.state { - State::Ready { - rust_style_buffer, - json_style_buffer, - .. - } => { - if let Err(err) = self.reset_style_editors( - &rust_style_buffer.clone(), - &json_style_buffer.clone(), - cx, - ) { - self.json_style_error = Some(format!("{err}").into()); - } else { - self.json_style_error = None; - } + if let State::Ready { + rust_style_buffer, + json_style_buffer, + .. + } = &self.state + { + if let Err(err) = + self.reset_style_editors(&rust_style_buffer.clone(), &json_style_buffer.clone(), cx) + { + self.json_style_error = Some(format!("{err}").into()); + } else { + self.json_style_error = None; } - _ => {} } } @@ -395,11 +389,11 @@ impl DivInspector { .zip(self.rust_completion_replace_range.as_ref()) { let before_text = snapshot - .text_for_range(0..completion_range.start.to_offset(&snapshot)) + .text_for_range(0..completion_range.start.to_offset(snapshot)) .collect::<String>(); let after_text = snapshot .text_for_range( - completion_range.end.to_offset(&snapshot) + completion_range.end.to_offset(snapshot) ..snapshot.clip_offset(usize::MAX, Bias::Left), ) .collect::<String>(); @@ -702,10 +696,10 @@ impl CompletionProvider for RustStyleCompletionProvider { } fn completion_replace_range(snapshot: &BufferSnapshot, anchor: &Anchor) -> Option<Range<Anchor>> { - let point = anchor.to_point(&snapshot); - let offset = point.to_offset(&snapshot); - let line_start = Point::new(point.row, 0).to_offset(&snapshot); - let line_end = Point::new(point.row, snapshot.line_len(point.row)).to_offset(&snapshot); + let point = anchor.to_point(snapshot); + let offset = point.to_offset(snapshot); + let line_start = Point::new(point.row, 0).to_offset(snapshot); + let line_end = Point::new(point.row, snapshot.line_len(point.row)).to_offset(snapshot); let mut lines = snapshot.text_for_range(line_start..line_end).lines(); let line = lines.next()?; diff --git a/crates/install_cli/src/install_cli.rs b/crates/install_cli/src/install_cli.rs index 12c094448b..dc9e0e31ab 100644 --- a/crates/install_cli/src/install_cli.rs +++ b/crates/install_cli/src/install_cli.rs @@ -105,7 +105,7 @@ pub fn install_cli(window: &mut Window, cx: &mut Context<Workspace>) { cx, ) })?; - register_zed_scheme(&cx).await.log_err(); + register_zed_scheme(cx).await.log_err(); Ok(()) }) .detach_and_prompt_err("Error installing zed cli", window, cx, |_, _, _| None); diff --git a/crates/jj/src/jj_repository.rs b/crates/jj/src/jj_repository.rs index 93ae79eb90..afbe54c99d 100644 --- a/crates/jj/src/jj_repository.rs +++ b/crates/jj/src/jj_repository.rs @@ -50,16 +50,13 @@ impl RealJujutsuRepository { impl JujutsuRepository for RealJujutsuRepository { fn list_bookmarks(&self) -> Vec<Bookmark> { - let bookmarks = self - .repository + self.repository .view() .bookmarks() .map(|(ref_name, _target)| Bookmark { ref_name: ref_name.as_str().to_string().into(), }) - .collect(); - - bookmarks + .collect() } } diff --git a/crates/jj/src/jj_store.rs b/crates/jj/src/jj_store.rs index a10f06fad4..2d2d958d7f 100644 --- a/crates/jj/src/jj_store.rs +++ b/crates/jj/src/jj_store.rs @@ -16,7 +16,7 @@ pub struct JujutsuStore { impl JujutsuStore { pub fn init_global(cx: &mut App) { - let Some(repository) = RealJujutsuRepository::new(&Path::new(".")).ok() else { + let Some(repository) = RealJujutsuRepository::new(Path::new(".")).ok() else { return; }; diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 0335a746cd..81dc36093b 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -170,23 +170,23 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap .await }; - if let Some(Some(Ok(item))) = opened.first() { - if let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) { - editor.update_in(cx, |editor, window, cx| { - let len = editor.buffer().read(cx).len(cx); - editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |s| s.select_ranges([len..len]), - ); - if len > 0 { - editor.insert("\n\n", window, cx); - } - editor.insert(&entry_heading, window, cx); + if let Some(Some(Ok(item))) = opened.first() + && let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) + { + editor.update_in(cx, |editor, window, cx| { + let len = editor.buffer().read(cx).len(cx); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([len..len]), + ); + if len > 0 { editor.insert("\n\n", window, cx); - })?; - } + } + editor.insert(&entry_heading, window, cx); + editor.insert("\n\n", window, cx); + })?; } anyhow::Ok(()) @@ -195,11 +195,9 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap } fn journal_dir(path: &str) -> Option<PathBuf> { - let expanded_journal_dir = shellexpand::full(path) //TODO handle this better + shellexpand::full(path) //TODO handle this better .ok() - .map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal")); - - expanded_journal_dir + .map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal")) } fn heading_entry(now: NaiveTime, hour_format: &Option<HourFormat>) -> String { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index e2bcc938fa..cc96022e63 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -716,7 +716,7 @@ impl EditPreview { &self.applied_edits_snapshot, &self.syntax_snapshot, None, - &syntax_theme, + syntax_theme, ); } @@ -727,7 +727,7 @@ impl EditPreview { ¤t_snapshot.text, ¤t_snapshot.syntax, Some(deletion_highlight_style), - &syntax_theme, + syntax_theme, ); } @@ -737,7 +737,7 @@ impl EditPreview { &self.applied_edits_snapshot, &self.syntax_snapshot, Some(insertion_highlight_style), - &syntax_theme, + syntax_theme, ); } @@ -749,7 +749,7 @@ impl EditPreview { &self.applied_edits_snapshot, &self.syntax_snapshot, None, - &syntax_theme, + syntax_theme, ); highlighted_text.build() @@ -1128,7 +1128,7 @@ impl Buffer { } else { ranges.as_slice() } - .into_iter() + .iter() .peekable(); let mut edits = Vec::new(); @@ -1158,13 +1158,12 @@ impl Buffer { base_buffer.edit(edits, None, cx) }); - if let Some(operation) = operation { - if let Some(BufferBranchState { + if let Some(operation) = operation + && let Some(BufferBranchState { merged_operations, .. }) = &mut self.branch_state - { - merged_operations.push(operation); - } + { + merged_operations.push(operation); } } @@ -1185,11 +1184,11 @@ impl Buffer { }; let mut operation_to_undo = None; - if let Operation::Buffer(text::Operation::Edit(operation)) = &operation { - if let Ok(ix) = merged_operations.binary_search(&operation.timestamp) { - merged_operations.remove(ix); - operation_to_undo = Some(operation.timestamp); - } + if let Operation::Buffer(text::Operation::Edit(operation)) = &operation + && let Ok(ix) = merged_operations.binary_search(&operation.timestamp) + { + merged_operations.remove(ix); + operation_to_undo = Some(operation.timestamp); } self.apply_ops([operation.clone()], cx); @@ -1396,7 +1395,8 @@ impl Buffer { is_first = false; return true; } - let any_sub_ranges_contain_range = layer + + layer .included_sub_ranges .map(|sub_ranges| { sub_ranges.iter().any(|sub_range| { @@ -1405,9 +1405,7 @@ impl Buffer { !is_before_start && !is_after_end }) }) - .unwrap_or(true); - let result = any_sub_ranges_contain_range; - return result; + .unwrap_or(true) }) .last() .map(|info| info.language.clone()) @@ -1424,10 +1422,10 @@ impl Buffer { .map(|info| info.language.clone()) .collect(); - if languages.is_empty() { - if let Some(buffer_language) = self.language() { - languages.push(buffer_language.clone()); - } + if languages.is_empty() + && let Some(buffer_language) = self.language() + { + languages.push(buffer_language.clone()); } languages @@ -1521,12 +1519,12 @@ impl Buffer { let new_syntax_map = parse_task.await; this.update(cx, move |this, cx| { let grammar_changed = - this.language.as_ref().map_or(true, |current_language| { + this.language.as_ref().is_none_or(|current_language| { !Arc::ptr_eq(&language, current_language) }); let language_registry_changed = new_syntax_map .contains_unknown_injections() - && language_registry.map_or(false, |registry| { + && language_registry.is_some_and(|registry| { registry.version() != new_syntax_map.language_registry_version() }); let parse_again = language_registry_changed @@ -1720,8 +1718,7 @@ impl Buffer { }) .with_delta(suggestion.delta, language_indent_size); - if old_suggestions.get(&new_row).map_or( - true, + if old_suggestions.get(&new_row).is_none_or( |(old_indentation, was_within_error)| { suggested_indent != *old_indentation && (!suggestion.within_error || *was_within_error) @@ -2015,7 +2012,7 @@ impl Buffer { fn was_changed(&mut self) { self.change_bits.retain(|change_bit| { - change_bit.upgrade().map_or(false, |bit| { + change_bit.upgrade().is_some_and(|bit| { bit.replace(true); true }) @@ -2192,7 +2189,7 @@ impl Buffer { if self .remote_selections .get(&self.text.replica_id()) - .map_or(true, |set| !set.selections.is_empty()) + .is_none_or(|set| !set.selections.is_empty()) { self.set_active_selections(Arc::default(), false, Default::default(), cx); } @@ -2589,10 +2586,10 @@ impl Buffer { line_mode, cursor_shape, } => { - if let Some(set) = self.remote_selections.get(&lamport_timestamp.replica_id) { - if set.lamport_timestamp > lamport_timestamp { - return; - } + if let Some(set) = self.remote_selections.get(&lamport_timestamp.replica_id) + && set.lamport_timestamp > lamport_timestamp + { + return; } self.remote_selections.insert( @@ -2618,7 +2615,7 @@ impl Buffer { self.completion_triggers = self .completion_triggers_per_language_server .values() - .flat_map(|triggers| triggers.into_iter().cloned()) + .flat_map(|triggers| triggers.iter().cloned()) .collect(); } else { self.completion_triggers_per_language_server @@ -2778,7 +2775,7 @@ impl Buffer { self.completion_triggers = self .completion_triggers_per_language_server .values() - .flat_map(|triggers| triggers.into_iter().cloned()) + .flat_map(|triggers| triggers.iter().cloned()) .collect(); } else { self.completion_triggers_per_language_server @@ -2840,7 +2837,7 @@ impl Buffer { let mut edits: Vec<(Range<usize>, String)> = Vec::new(); let mut last_end = None; for _ in 0..old_range_count { - if last_end.map_or(false, |last_end| last_end >= self.len()) { + if last_end.is_some_and(|last_end| last_end >= self.len()) { break; } @@ -3060,14 +3057,14 @@ impl BufferSnapshot { if config .decrease_indent_pattern .as_ref() - .map_or(false, |regex| regex.is_match(line)) + .is_some_and(|regex| regex.is_match(line)) { indent_change_rows.push((row, Ordering::Less)); } if config .increase_indent_pattern .as_ref() - .map_or(false, |regex| regex.is_match(line)) + .is_some_and(|regex| regex.is_match(line)) { indent_change_rows.push((row + 1, Ordering::Greater)); } @@ -3083,7 +3080,7 @@ impl BufferSnapshot { } } for rule in &config.decrease_indent_patterns { - if rule.pattern.as_ref().map_or(false, |r| r.is_match(line)) { + if rule.pattern.as_ref().is_some_and(|r| r.is_match(line)) { let row_start_column = self.indent_size_for_line(row).len; let basis_row = rule .valid_after @@ -3296,8 +3293,7 @@ impl BufferSnapshot { range: Range<D>, ) -> Option<SyntaxLayer<'_>> { let range = range.to_offset(self); - return self - .syntax + self.syntax .layers_for_range(range, &self.text, false) .max_by(|a, b| { if a.depth != b.depth { @@ -3307,7 +3303,7 @@ impl BufferSnapshot { } else { a.node().end_byte().cmp(&b.node().end_byte()).reverse() } - }); + }) } /// Returns the main [`Language`]. @@ -3365,9 +3361,8 @@ impl BufferSnapshot { } } - if let Some(range) = range { - if smallest_range_and_depth.as_ref().map_or( - true, + if let Some(range) = range + && smallest_range_and_depth.as_ref().is_none_or( |(smallest_range, smallest_range_depth)| { if layer.depth > *smallest_range_depth { true @@ -3377,13 +3372,13 @@ impl BufferSnapshot { false } }, - ) { - smallest_range_and_depth = Some((range, layer.depth)); - scope = Some(LanguageScope { - language: layer.language.clone(), - override_id: layer.override_id(offset, &self.text), - }); - } + ) + { + smallest_range_and_depth = Some((range, layer.depth)); + scope = Some(LanguageScope { + language: layer.language.clone(), + override_id: layer.override_id(offset, &self.text), + }); } } @@ -3499,17 +3494,17 @@ impl BufferSnapshot { // If there is a candidate node on both sides of the (empty) range, then // decide between the two by favoring a named node over an anonymous token. // If both nodes are the same in that regard, favor the right one. - if let Some(right_node) = right_node { - if right_node.is_named() || !left_node.is_named() { - layer_result = right_node; - } + if let Some(right_node) = right_node + && (right_node.is_named() || !left_node.is_named()) + { + layer_result = right_node; } } - if let Some(previous_result) = &result { - if previous_result.byte_range().len() < layer_result.byte_range().len() { - continue; - } + if let Some(previous_result) = &result + && previous_result.byte_range().len() < layer_result.byte_range().len() + { + continue; } result = Some(layer_result); } @@ -3544,7 +3539,7 @@ impl BufferSnapshot { } } - return Some(cursor.node()); + Some(cursor.node()) } /// Returns the outline for the buffer. @@ -3573,7 +3568,7 @@ impl BufferSnapshot { )?; let mut prev_depth = None; items.retain(|item| { - let result = prev_depth.map_or(true, |prev_depth| item.depth > prev_depth); + let result = prev_depth.is_none_or(|prev_depth| item.depth > prev_depth); prev_depth = Some(item.depth); result }); @@ -4081,10 +4076,10 @@ impl BufferSnapshot { let mut result: Option<(Range<usize>, Range<usize>)> = None; for pair in self.enclosing_bracket_ranges(range.clone()) { - if let Some(range_filter) = range_filter { - if !range_filter(pair.open_range.clone(), pair.close_range.clone()) { - continue; - } + if let Some(range_filter) = range_filter + && !range_filter(pair.open_range.clone(), pair.close_range.clone()) + { + continue; } let len = pair.close_range.end - pair.open_range.start; @@ -4450,7 +4445,7 @@ impl BufferSnapshot { pub fn words_in_range(&self, query: WordsQuery) -> BTreeMap<String, Range<Anchor>> { let query_str = query.fuzzy_contents; - if query_str.map_or(false, |query| query.is_empty()) { + if query_str.is_some_and(|query| query.is_empty()) { return BTreeMap::default(); } @@ -4474,27 +4469,26 @@ impl BufferSnapshot { current_word_start_ix = Some(ix); } - if let Some(query_chars) = &query_chars { - if query_ix < query_len { - if c.to_lowercase().eq(query_chars[query_ix].to_lowercase()) { - query_ix += 1; - } - } + if let Some(query_chars) = &query_chars + && query_ix < query_len + && c.to_lowercase().eq(query_chars[query_ix].to_lowercase()) + { + query_ix += 1; } continue; - } else if let Some(word_start) = current_word_start_ix.take() { - if query_ix == query_len { - let word_range = self.anchor_before(word_start)..self.anchor_after(ix); - let mut word_text = self.text_for_range(word_start..ix).peekable(); - let first_char = word_text - .peek() - .and_then(|first_chunk| first_chunk.chars().next()); - // Skip empty and "words" starting with digits as a heuristic to reduce useless completions - if !query.skip_digits - || first_char.map_or(true, |first_char| !first_char.is_digit(10)) - { - words.insert(word_text.collect(), word_range); - } + } else if let Some(word_start) = current_word_start_ix.take() + && query_ix == query_len + { + let word_range = self.anchor_before(word_start)..self.anchor_after(ix); + let mut word_text = self.text_for_range(word_start..ix).peekable(); + let first_char = word_text + .peek() + .and_then(|first_chunk| first_chunk.chars().next()); + // Skip empty and "words" starting with digits as a heuristic to reduce useless completions + if !query.skip_digits + || first_char.is_none_or(|first_char| !first_char.is_digit(10)) + { + words.insert(word_text.collect(), word_range); } } query_ix = 0; @@ -4607,17 +4601,17 @@ impl<'a> BufferChunks<'a> { highlights .stack .retain(|(end_offset, _)| *end_offset > range.start); - if let Some(capture) = &highlights.next_capture { - if range.start >= capture.node.start_byte() { - let next_capture_end = capture.node.end_byte(); - if range.start < next_capture_end { - highlights.stack.push(( - next_capture_end, - highlights.highlight_maps[capture.grammar_index].get(capture.index), - )); - } - highlights.next_capture.take(); + if let Some(capture) = &highlights.next_capture + && range.start >= capture.node.start_byte() + { + let next_capture_end = capture.node.end_byte(); + if range.start < next_capture_end { + highlights.stack.push(( + next_capture_end, + highlights.highlight_maps[capture.grammar_index].get(capture.index), + )); } + highlights.next_capture.take(); } } else if let Some(snapshot) = self.buffer_snapshot { let (captures, highlight_maps) = snapshot.get_highlights(self.range.clone()); @@ -4642,33 +4636,33 @@ impl<'a> BufferChunks<'a> { } fn initialize_diagnostic_endpoints(&mut self) { - if let Some(diagnostics) = self.diagnostic_endpoints.as_mut() { - if let Some(buffer) = self.buffer_snapshot { - let mut diagnostic_endpoints = Vec::new(); - for entry in buffer.diagnostics_in_range::<_, usize>(self.range.clone(), false) { - diagnostic_endpoints.push(DiagnosticEndpoint { - offset: entry.range.start, - is_start: true, - severity: entry.diagnostic.severity, - is_unnecessary: entry.diagnostic.is_unnecessary, - underline: entry.diagnostic.underline, - }); - diagnostic_endpoints.push(DiagnosticEndpoint { - offset: entry.range.end, - is_start: false, - severity: entry.diagnostic.severity, - is_unnecessary: entry.diagnostic.is_unnecessary, - underline: entry.diagnostic.underline, - }); - } - diagnostic_endpoints - .sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start)); - *diagnostics = diagnostic_endpoints.into_iter().peekable(); - self.hint_depth = 0; - self.error_depth = 0; - self.warning_depth = 0; - self.information_depth = 0; + if let Some(diagnostics) = self.diagnostic_endpoints.as_mut() + && let Some(buffer) = self.buffer_snapshot + { + let mut diagnostic_endpoints = Vec::new(); + for entry in buffer.diagnostics_in_range::<_, usize>(self.range.clone(), false) { + diagnostic_endpoints.push(DiagnosticEndpoint { + offset: entry.range.start, + is_start: true, + severity: entry.diagnostic.severity, + is_unnecessary: entry.diagnostic.is_unnecessary, + underline: entry.diagnostic.underline, + }); + diagnostic_endpoints.push(DiagnosticEndpoint { + offset: entry.range.end, + is_start: false, + severity: entry.diagnostic.severity, + is_unnecessary: entry.diagnostic.is_unnecessary, + underline: entry.diagnostic.underline, + }); } + diagnostic_endpoints + .sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start)); + *diagnostics = diagnostic_endpoints.into_iter().peekable(); + self.hint_depth = 0; + self.error_depth = 0; + self.warning_depth = 0; + self.information_depth = 0; } } @@ -4779,11 +4773,11 @@ impl<'a> Iterator for BufferChunks<'a> { .min(next_capture_start) .min(next_diagnostic_endpoint); let mut highlight_id = None; - if let Some(highlights) = self.highlights.as_ref() { - if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() { - chunk_end = chunk_end.min(*parent_capture_end); - highlight_id = Some(*parent_highlight_id); - } + if let Some(highlights) = self.highlights.as_ref() + && let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() + { + chunk_end = chunk_end.min(*parent_capture_end); + highlight_id = Some(*parent_highlight_id); } let slice = @@ -4977,11 +4971,12 @@ pub(crate) fn contiguous_ranges( std::iter::from_fn(move || { loop { if let Some(value) = values.next() { - if let Some(range) = &mut current_range { - if value == range.end && range.len() < max_len { - range.end += 1; - continue; - } + if let Some(range) = &mut current_range + && value == range.end + && range.len() < max_len + { + range.end += 1; + continue; } let prev_range = current_range.clone(); @@ -5049,10 +5044,10 @@ impl CharClassifier { } else { scope.word_characters() }; - if let Some(characters) = characters { - if characters.contains(&c) { - return CharKind::Word; - } + if let Some(characters) = characters + && characters.contains(&c) + { + return CharKind::Word; } } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 6fa31da860..87fc846a53 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -121,8 +121,8 @@ where func(cursor.deref_mut()) } -static NEXT_LANGUAGE_ID: LazyLock<AtomicUsize> = LazyLock::new(Default::default); -static NEXT_GRAMMAR_ID: LazyLock<AtomicUsize> = LazyLock::new(Default::default); +static NEXT_LANGUAGE_ID: AtomicUsize = AtomicUsize::new(0); +static NEXT_GRAMMAR_ID: AtomicUsize = AtomicUsize::new(0); static WASM_ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(|| { wasmtime::Engine::new(&wasmtime::Config::new()).expect("Failed to create Wasmtime engine") }); @@ -329,9 +329,9 @@ pub trait LspAdapter: 'static + Send + Sync { // We only want to cache when we fall back to the global one, // because we don't want to download and overwrite our global one // for each worktree we might have open. - if binary_options.allow_path_lookup { - if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), toolchains, cx).await { - log::info!( + if binary_options.allow_path_lookup + && let Some(binary) = self.check_if_user_installed(delegate.as_ref(), toolchains, cx).await { + log::debug!( "found user-installed language server for {}. path: {:?}, arguments: {:?}", self.name().0, binary.path, @@ -339,7 +339,6 @@ pub trait LspAdapter: 'static + Send + Sync { ); return Ok(binary); } - } anyhow::ensure!(binary_options.allow_binary_download, "downloading language servers disabled"); @@ -602,7 +601,7 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized> } let name = adapter.name(); - log::info!("fetching latest version of language server {:?}", name.0); + log::debug!("fetching latest version of language server {:?}", name.0); delegate.update_status(name.clone(), BinaryStatus::CheckingForUpdate); let latest_version = adapter @@ -613,7 +612,7 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized> .check_if_version_installed(latest_version.as_ref(), &container_dir, delegate.as_ref()) .await { - log::info!("language server {:?} is already installed", name.0); + log::debug!("language server {:?} is already installed", name.0); delegate.update_status(name.clone(), BinaryStatus::None); Ok(binary) } else { @@ -964,11 +963,11 @@ where fn deserialize_regex_vec<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Regex>, D::Error> { let sources = Vec::<String>::deserialize(d)?; - let mut regexes = Vec::new(); - for source in sources { - regexes.push(regex::Regex::new(&source).map_err(de::Error::custom)?); - } - Ok(regexes) + sources + .into_iter() + .map(|source| regex::Regex::new(&source)) + .collect::<Result<_, _>>() + .map_err(de::Error::custom) } fn regex_vec_json_schema(_: &mut SchemaGenerator) -> schemars::Schema { @@ -1034,12 +1033,10 @@ impl<'de> Deserialize<'de> for BracketPairConfig { D: Deserializer<'de>, { let result = Vec::<BracketPairContent>::deserialize(deserializer)?; - let mut brackets = Vec::with_capacity(result.len()); - let mut disabled_scopes_by_bracket_ix = Vec::with_capacity(result.len()); - for entry in result { - brackets.push(entry.bracket_pair); - disabled_scopes_by_bracket_ix.push(entry.not_in); - } + let (brackets, disabled_scopes_by_bracket_ix) = result + .into_iter() + .map(|entry| (entry.bracket_pair, entry.not_in)) + .unzip(); Ok(BracketPairConfig { pairs: brackets, @@ -1379,16 +1376,14 @@ impl Language { let grammar = self.grammar_mut().context("cannot mutate grammar")?; let query = Query::new(&grammar.ts_language, source)?; - let mut extra_captures = Vec::with_capacity(query.capture_names().len()); - - for name in query.capture_names().iter() { - let kind = if *name == "run" { - RunnableCapture::Run - } else { - RunnableCapture::Named(name.to_string().into()) - }; - extra_captures.push(kind); - } + let extra_captures: Vec<_> = query + .capture_names() + .iter() + .map(|&name| match name { + "run" => RunnableCapture::Run, + name => RunnableCapture::Named(name.to_string().into()), + }) + .collect(); grammar.runnable_config = Some(RunnableConfig { extra_captures, @@ -1518,9 +1513,8 @@ impl Language { .map(|ix| { let mut config = BracketsPatternConfig::default(); for setting in query.property_settings(ix) { - match setting.key.as_ref() { - "newline.only" => config.newline_only = true, - _ => {} + if setting.key.as_ref() == "newline.only" { + config.newline_only = true } } config @@ -1780,10 +1774,10 @@ impl Language { BufferChunks::new(text, range, Some((captures, highlight_maps)), false, None) { let end_offset = offset + chunk.text.len(); - if let Some(highlight_id) = chunk.syntax_highlight_id { - if !highlight_id.is_default() { - result.push((offset..end_offset, highlight_id)); - } + if let Some(highlight_id) = chunk.syntax_highlight_id + && !highlight_id.is_default() + { + result.push((offset..end_offset, highlight_id)); } offset = end_offset; } @@ -1800,11 +1794,11 @@ impl Language { } pub fn set_theme(&self, theme: &SyntaxTheme) { - if let Some(grammar) = self.grammar.as_ref() { - if let Some(highlights_query) = &grammar.highlights_query { - *grammar.highlight_map.lock() = - HighlightMap::new(highlights_query.capture_names(), theme); - } + if let Some(grammar) = self.grammar.as_ref() + && let Some(highlights_query) = &grammar.highlights_query + { + *grammar.highlight_map.lock() = + HighlightMap::new(highlights_query.capture_names(), theme); } } @@ -1834,7 +1828,7 @@ impl Language { impl LanguageScope { pub fn path_suffixes(&self) -> &[String] { - &self.language.path_suffixes() + self.language.path_suffixes() } pub fn language_name(&self) -> LanguageName { @@ -1924,11 +1918,11 @@ impl LanguageScope { .enumerate() .map(move |(ix, bracket)| { let mut is_enabled = true; - if let Some(next_disabled_ix) = disabled_ids.first() { - if ix == *next_disabled_ix as usize { - disabled_ids = &disabled_ids[1..]; - is_enabled = false; - } + if let Some(next_disabled_ix) = disabled_ids.first() + && ix == *next_disabled_ix as usize + { + disabled_ids = &disabled_ids[1..]; + is_enabled = false; } (bracket, is_enabled) }) diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 6a89b90462..589fc68e99 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -773,7 +773,7 @@ impl LanguageRegistry { }; let content_matches = || { - config.first_line_pattern.as_ref().map_or(false, |pattern| { + config.first_line_pattern.as_ref().is_some_and(|pattern| { content .as_ref() .is_some_and(|content| pattern.is_match(content)) @@ -1102,7 +1102,7 @@ impl LanguageRegistry { use gpui::AppContext as _; let mut state = self.state.write(); - let fake_entry = state.fake_server_entries.get_mut(&name)?; + let fake_entry = state.fake_server_entries.get_mut(name)?; let (server, mut fake_server) = lsp::FakeLanguageServer::new( server_id, binary, diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 29669ba2a0..fbb67a9818 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -187,8 +187,8 @@ impl LanguageSettings { let rest = available_language_servers .iter() .filter(|&available_language_server| { - !disabled_language_servers.contains(&available_language_server) - && !enabled_language_servers.contains(&available_language_server) + !disabled_language_servers.contains(available_language_server) + && !enabled_language_servers.contains(available_language_server) }) .cloned() .collect::<Vec<_>>(); @@ -253,7 +253,7 @@ impl EditPredictionSettings { !self.disabled_globs.iter().any(|glob| { if glob.is_absolute { file.as_local() - .map_or(false, |local| glob.matcher.is_match(local.abs_path(cx))) + .is_some_and(|local| glob.matcher.is_match(local.abs_path(cx))) } else { glob.matcher.is_match(file.path()) } diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 18f6bb8709..3be189cea0 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -86,7 +86,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation { proto::operation::UpdateCompletionTriggers { replica_id: lamport_timestamp.replica_id as u32, lamport_timestamp: lamport_timestamp.value, - triggers: triggers.iter().cloned().collect(), + triggers: triggers.clone(), language_server_id: server_id.to_proto(), }, ), @@ -385,12 +385,10 @@ pub fn deserialize_undo_map_entry( /// Deserializes selections from the RPC representation. pub fn deserialize_selections(selections: Vec<proto::Selection>) -> Arc<[Selection<Anchor>]> { - Arc::from( - selections - .into_iter() - .filter_map(deserialize_selection) - .collect::<Vec<_>>(), - ) + selections + .into_iter() + .filter_map(deserialize_selection) + .collect() } /// Deserializes a [`Selection`] from the RPC representation. diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index c56ffed066..f10056af13 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -414,42 +414,42 @@ impl SyntaxSnapshot { .collect::<Vec<_>>(); self.reparse_with_ranges(text, root_language.clone(), edit_ranges, registry.as_ref()); - if let Some(registry) = registry { - if registry.version() != self.language_registry_version { - let mut resolved_injection_ranges = Vec::new(); - let mut cursor = self - .layers - .filter::<_, ()>(text, |summary| summary.contains_unknown_injections); + if let Some(registry) = registry + && registry.version() != self.language_registry_version + { + let mut resolved_injection_ranges = Vec::new(); + let mut cursor = self + .layers + .filter::<_, ()>(text, |summary| summary.contains_unknown_injections); + cursor.next(); + while let Some(layer) = cursor.item() { + let SyntaxLayerContent::Pending { language_name } = &layer.content else { + unreachable!() + }; + if registry + .language_for_name_or_extension(language_name) + .now_or_never() + .and_then(|language| language.ok()) + .is_some() + { + let range = layer.range.to_offset(text); + log::trace!("reparse range {range:?} for language {language_name:?}"); + resolved_injection_ranges.push(range); + } + cursor.next(); - while let Some(layer) = cursor.item() { - let SyntaxLayerContent::Pending { language_name } = &layer.content else { - unreachable!() - }; - if registry - .language_for_name_or_extension(language_name) - .now_or_never() - .and_then(|language| language.ok()) - .is_some() - { - let range = layer.range.to_offset(text); - log::trace!("reparse range {range:?} for language {language_name:?}"); - resolved_injection_ranges.push(range); - } - - cursor.next(); - } - drop(cursor); - - if !resolved_injection_ranges.is_empty() { - self.reparse_with_ranges( - text, - root_language, - resolved_injection_ranges, - Some(®istry), - ); - } - self.language_registry_version = registry.version(); } + drop(cursor); + + if !resolved_injection_ranges.is_empty() { + self.reparse_with_ranges( + text, + root_language, + resolved_injection_ranges, + Some(®istry), + ); + } + self.language_registry_version = registry.version(); } self.update_count += 1; @@ -1065,10 +1065,10 @@ impl<'a> SyntaxMapCaptures<'a> { pub fn set_byte_range(&mut self, range: Range<usize>) { for layer in &mut self.layers { layer.captures.set_byte_range(range.clone()); - if let Some(capture) = &layer.next_capture { - if capture.node.end_byte() > range.start { - continue; - } + if let Some(capture) = &layer.next_capture + && capture.node.end_byte() > range.start + { + continue; } layer.advance(); } @@ -1277,11 +1277,11 @@ fn join_ranges( (None, None) => break, }; - if let Some(last) = result.last_mut() { - if range.start <= last.end { - last.end = last.end.max(range.end); - continue; - } + if let Some(last) = result.last_mut() + && range.start <= last.end + { + last.end = last.end.max(range.end); + continue; } result.push(range); } @@ -1297,7 +1297,7 @@ fn parse_text( ) -> anyhow::Result<Tree> { with_parser(|parser| { let mut chunks = text.chunks_in_range(start_byte..text.len()); - parser.set_included_ranges(&ranges)?; + parser.set_included_ranges(ranges)?; parser.set_language(&grammar.ts_language)?; parser .parse_with_options( @@ -1330,14 +1330,13 @@ fn get_injections( // if there currently no matches for that injection. combined_injection_ranges.clear(); for pattern in &config.patterns { - if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) { - if let Some(language) = language_registry + if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) + && let Some(language) = language_registry .language_for_name_or_extension(language_name) .now_or_never() .and_then(|language| language.ok()) - { - combined_injection_ranges.insert(language.id, (language, Vec::new())); - } + { + combined_injection_ranges.insert(language.id, (language, Vec::new())); } } @@ -1357,10 +1356,11 @@ fn get_injections( content_ranges.first().unwrap().start_byte..content_ranges.last().unwrap().end_byte; // Avoid duplicate matches if two changed ranges intersect the same injection. - if let Some((prev_pattern_ix, prev_range)) = &prev_match { - if mat.pattern_index == *prev_pattern_ix && content_range == *prev_range { - continue; - } + if let Some((prev_pattern_ix, prev_range)) = &prev_match + && mat.pattern_index == *prev_pattern_ix + && content_range == *prev_range + { + continue; } prev_match = Some((mat.pattern_index, content_range.clone())); @@ -1630,10 +1630,8 @@ impl<'a> SyntaxLayer<'a> { if offset < range.start || offset > range.end { continue; } - } else { - if offset <= range.start || offset >= range.end { - continue; - } + } else if offset <= range.start || offset >= range.end { + continue; } if let Some((_, smallest_range)) = &smallest_match { diff --git a/crates/language/src/text_diff.rs b/crates/language/src/text_diff.rs index f9221f571a..1e3e12758d 100644 --- a/crates/language/src/text_diff.rs +++ b/crates/language/src/text_diff.rs @@ -154,19 +154,19 @@ fn diff_internal( input, |old_tokens: Range<u32>, new_tokens: Range<u32>| { old_offset += token_len( - &input, + input, &input.before[old_token_ix as usize..old_tokens.start as usize], ); new_offset += token_len( - &input, + input, &input.after[new_token_ix as usize..new_tokens.start as usize], ); let old_len = token_len( - &input, + input, &input.before[old_tokens.start as usize..old_tokens.end as usize], ); let new_len = token_len( - &input, + input, &input.after[new_tokens.start as usize..new_tokens.end as usize], ); let old_byte_range = old_offset..old_offset + old_len; @@ -189,11 +189,11 @@ fn tokenize(text: &str, language_scope: Option<LanguageScope>) -> impl Iterator< while let Some((ix, c)) = chars.next() { let mut token = None; let kind = classifier.kind(c); - if let Some((prev_char, prev_kind)) = prev { - if kind != prev_kind || (kind == CharKind::Punctuation && c != prev_char) { - token = Some(&text[start_ix..ix]); - start_ix = ix; - } + if let Some((prev_char, prev_kind)) = prev + && (kind != prev_kind || (kind == CharKind::Punctuation && c != prev_char)) + { + token = Some(&text[start_ix..ix]); + start_ix = ix; } prev = Some((c, kind)); if token.is_some() { diff --git a/crates/language_extension/src/language_extension.rs b/crates/language_extension/src/language_extension.rs index 7bca0eb485..510f870ce8 100644 --- a/crates/language_extension/src/language_extension.rs +++ b/crates/language_extension/src/language_extension.rs @@ -61,6 +61,6 @@ impl ExtensionLanguageProxy for LanguageServerRegistryProxy { grammars_to_remove: &[Arc<str>], ) { self.language_registry - .remove_languages(&languages_to_remove, &grammars_to_remove); + .remove_languages(languages_to_remove, grammars_to_remove); } } diff --git a/crates/language_model/src/fake_provider.rs b/crates/language_model/src/fake_provider.rs index a9c7d5c034..ebfd37d16c 100644 --- a/crates/language_model/src/fake_provider.rs +++ b/crates/language_model/src/fake_provider.rs @@ -1,13 +1,14 @@ use crate::{ - AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, - LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, - LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, - LanguageModelToolChoice, + AuthenticateError, ConfigurationViewTargetAgent, LanguageModel, LanguageModelCompletionError, + LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider, + LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, + LanguageModelRequest, LanguageModelToolChoice, }; -use futures::{FutureExt, StreamExt, channel::mpsc, future::BoxFuture, stream::BoxStream}; +use futures::{FutureExt, channel::mpsc, future::BoxFuture, stream::BoxStream}; use gpui::{AnyView, App, AsyncApp, Entity, Task, Window}; use http_client::Result; use parking_lot::Mutex; +use smol::stream::StreamExt; use std::sync::Arc; #[derive(Clone)] @@ -62,7 +63,12 @@ impl LanguageModelProvider for FakeLanguageModelProvider { Task::ready(Ok(())) } - fn configuration_view(&self, _window: &mut Window, _: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: ConfigurationViewTargetAgent, + _window: &mut Window, + _: &mut App, + ) -> AnyView { unimplemented!() } @@ -95,7 +101,9 @@ pub struct FakeLanguageModel { current_completion_txs: Mutex< Vec<( LanguageModelRequest, - mpsc::UnboundedSender<LanguageModelCompletionEvent>, + mpsc::UnboundedSender< + Result<LanguageModelCompletionEvent, LanguageModelCompletionError>, + >, )>, >, } @@ -145,7 +153,21 @@ impl FakeLanguageModel { .find(|(req, _)| req == request) .map(|(_, tx)| tx) .unwrap(); - tx.unbounded_send(event.into()).unwrap(); + tx.unbounded_send(Ok(event.into())).unwrap(); + } + + pub fn send_completion_stream_error( + &self, + request: &LanguageModelRequest, + error: impl Into<LanguageModelCompletionError>, + ) { + let current_completion_txs = self.current_completion_txs.lock(); + let tx = current_completion_txs + .iter() + .find(|(req, _)| req == request) + .map(|(_, tx)| tx) + .unwrap(); + tx.unbounded_send(Err(error.into())).unwrap(); } pub fn end_completion_stream(&self, request: &LanguageModelRequest) { @@ -165,6 +187,13 @@ impl FakeLanguageModel { self.send_completion_stream_event(self.pending_completions().last().unwrap(), event); } + pub fn send_last_completion_stream_error( + &self, + error: impl Into<LanguageModelCompletionError>, + ) { + self.send_completion_stream_error(self.pending_completions().last().unwrap(), error); + } + pub fn end_last_completion_stream(&self) { self.end_completion_stream(self.pending_completions().last().unwrap()); } @@ -224,7 +253,7 @@ impl LanguageModel for FakeLanguageModel { > { let (tx, rx) = mpsc::unbounded(); self.current_completion_txs.lock().push((request, tx)); - async move { Ok(rx.map(Ok).boxed()) }.boxed() + async move { Ok(rx.boxed()) }.boxed() } fn as_fake(&self) -> &Self { diff --git a/crates/language_model/src/language_model.rs b/crates/language_model/src/language_model.rs index 1637d2de8a..b10529c3d9 100644 --- a/crates/language_model/src/language_model.rs +++ b/crates/language_model/src/language_model.rs @@ -300,7 +300,7 @@ impl From<AnthropicError> for LanguageModelCompletionError { }, AnthropicError::ServerOverloaded { retry_after } => Self::ServerOverloaded { provider, - retry_after: retry_after, + retry_after, }, AnthropicError::ApiError(api_error) => api_error.into(), } @@ -634,7 +634,12 @@ pub trait LanguageModelProvider: 'static { } fn is_authenticated(&self, cx: &App) -> bool; fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>>; - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView; + fn configuration_view( + &self, + target_agent: ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView; fn must_accept_terms(&self, _cx: &App) -> bool { false } @@ -648,6 +653,13 @@ pub trait LanguageModelProvider: 'static { fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>>; } +#[derive(Default, Clone, Copy)] +pub enum ConfigurationViewTargetAgent { + #[default] + ZedAgent, + Other(&'static str), +} + #[derive(PartialEq, Eq)] pub enum LanguageModelProviderTosView { /// When there are some past interactions in the Agent Panel. diff --git a/crates/language_model/src/registry.rs b/crates/language_model/src/registry.rs index 7cf071808a..8f52f8c1c3 100644 --- a/crates/language_model/src/registry.rs +++ b/crates/language_model/src/registry.rs @@ -21,7 +21,7 @@ impl Global for GlobalLanguageModelRegistry {} pub enum ConfigurationError { #[error("Configure at least one LLM provider to start using the panel.")] NoProvider, - #[error("LLM Provider is not configured or does not support the configured model.")] + #[error("LLM provider is not configured or does not support the configured model.")] ModelNotFound, #[error("{} LLM provider is not configured.", .0.name().0)] ProviderNotAuthenticated(Arc<dyn LanguageModelProvider>), @@ -107,7 +107,7 @@ pub enum Event { InlineAssistantModelChanged, CommitMessageModelChanged, ThreadSummaryModelChanged, - ProviderStateChanged, + ProviderStateChanged(LanguageModelProviderId), AddedProvider(LanguageModelProviderId), RemovedProvider(LanguageModelProviderId), } @@ -148,8 +148,11 @@ impl LanguageModelRegistry { ) { let id = provider.id(); - let subscription = provider.subscribe(cx, |_, cx| { - cx.emit(Event::ProviderStateChanged); + let subscription = provider.subscribe(cx, { + let id = id.clone(); + move |_, cx| { + cx.emit(Event::ProviderStateChanged(id.clone())); + } }); if let Some(subscription) = subscription { subscription.detach(); diff --git a/crates/language_model/src/request.rs b/crates/language_model/src/request.rs index edce3d03b7..1182e0f7a8 100644 --- a/crates/language_model/src/request.rs +++ b/crates/language_model/src/request.rs @@ -220,42 +220,39 @@ impl<'de> Deserialize<'de> for LanguageModelToolResultContent { // Accept wrapped text format: { "type": "text", "text": "..." } if let (Some(type_value), Some(text_value)) = - (get_field(&obj, "type"), get_field(&obj, "text")) + (get_field(obj, "type"), get_field(obj, "text")) + && let Some(type_str) = type_value.as_str() + && type_str.to_lowercase() == "text" + && let Some(text) = text_value.as_str() { - if let Some(type_str) = type_value.as_str() { - if type_str.to_lowercase() == "text" { - if let Some(text) = text_value.as_str() { - return Ok(Self::Text(Arc::from(text))); - } - } - } + return Ok(Self::Text(Arc::from(text))); } // Check for wrapped Text variant: { "text": "..." } - if let Some((_key, value)) = obj.iter().find(|(k, _)| k.to_lowercase() == "text") { - if obj.len() == 1 { - // Only one field, and it's "text" (case-insensitive) - if let Some(text) = value.as_str() { - return Ok(Self::Text(Arc::from(text))); - } + if let Some((_key, value)) = obj.iter().find(|(k, _)| k.to_lowercase() == "text") + && obj.len() == 1 + { + // Only one field, and it's "text" (case-insensitive) + if let Some(text) = value.as_str() { + return Ok(Self::Text(Arc::from(text))); } } // Check for wrapped Image variant: { "image": { "source": "...", "size": ... } } - if let Some((_key, value)) = obj.iter().find(|(k, _)| k.to_lowercase() == "image") { - if obj.len() == 1 { - // Only one field, and it's "image" (case-insensitive) - // Try to parse the nested image object - if let Some(image_obj) = value.as_object() { - if let Some(image) = LanguageModelImage::from_json(image_obj) { - return Ok(Self::Image(image)); - } - } + if let Some((_key, value)) = obj.iter().find(|(k, _)| k.to_lowercase() == "image") + && obj.len() == 1 + { + // Only one field, and it's "image" (case-insensitive) + // Try to parse the nested image object + if let Some(image_obj) = value.as_object() + && let Some(image) = LanguageModelImage::from_json(image_obj) + { + return Ok(Self::Image(image)); } } // Try as direct Image (object with "source" and "size" fields) - if let Some(image) = LanguageModelImage::from_json(&obj) { + if let Some(image) = LanguageModelImage::from_json(obj) { return Ok(Self::Image(image)); } } @@ -272,7 +269,7 @@ impl<'de> Deserialize<'de> for LanguageModelToolResultContent { impl LanguageModelToolResultContent { pub fn to_str(&self) -> Option<&str> { match self { - Self::Text(text) => Some(&text), + Self::Text(text) => Some(text), Self::Image(_) => None, } } diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index ef21e85f71..0d061c0587 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -15,11 +15,11 @@ use gpui::{ }; use http_client::HttpClient; use language_model::{ - AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, - LanguageModelCompletionError, LanguageModelId, LanguageModelName, LanguageModelProvider, - LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, - LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent, MessageContent, - RateLimiter, Role, + AuthenticateError, ConfigurationViewTargetAgent, LanguageModel, + LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelId, + LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice, + LanguageModelToolResultContent, MessageContent, RateLimiter, Role, }; use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason}; use schemars::JsonSchema; @@ -114,7 +114,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .ok(); this.update(cx, |this, cx| { @@ -133,7 +133,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await .ok(); @@ -153,29 +153,14 @@ impl State { return Task::ready(Ok(())); } - let credentials_provider = <dyn CredentialsProvider>::global(cx); - let api_url = AllLanguageModelSettings::get_global(cx) - .anthropic - .api_url - .clone(); + let key = AnthropicLanguageModelProvider::api_key(cx); cx.spawn(async move |this, cx| { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(ANTHROPIC_API_KEY_VAR) { - (api_key, true) - } else { - let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) - .await? - .ok_or(AuthenticateError::CredentialsNotFound)?; - ( - String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, - false, - ) - }; + let key = key.await?; this.update(cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; + this.api_key = Some(key.key); + this.api_key_from_env = key.from_env; cx.notify(); })?; @@ -184,6 +169,11 @@ impl State { } } +pub struct ApiKey { + pub key: String, + pub from_env: bool, +} + impl AnthropicLanguageModelProvider { pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self { let state = cx.new(|cx| State { @@ -206,6 +196,33 @@ impl AnthropicLanguageModelProvider { request_limiter: RateLimiter::new(4), }) } + + pub fn api_key(cx: &mut App) -> Task<Result<ApiKey>> { + let credentials_provider = <dyn CredentialsProvider>::global(cx); + let api_url = AllLanguageModelSettings::get_global(cx) + .anthropic + .api_url + .clone(); + + if let Ok(key) = std::env::var(ANTHROPIC_API_KEY_VAR) { + Task::ready(Ok(ApiKey { + key, + from_env: true, + })) + } else { + cx.spawn(async move |cx| { + let (_, api_key) = credentials_provider + .read_credentials(&api_url, cx) + .await? + .ok_or(AuthenticateError::CredentialsNotFound)?; + + Ok(ApiKey { + key: String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, + from_env: false, + }) + }) + } + } } impl LanguageModelProviderState for AnthropicLanguageModelProvider { @@ -299,8 +316,13 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { - cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) + fn configuration_view( + &self, + target_agent: ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { + cx.new(|cx| ConfigurationView::new(self.state.clone(), target_agent, window, cx)) .into() } @@ -532,7 +554,7 @@ pub fn into_anthropic( .into_iter() .filter_map(|content| match content { MessageContent::Text(text) => { - let text = if text.chars().last().map_or(false, |c| c.is_whitespace()) { + let text = if text.chars().last().is_some_and(|c| c.is_whitespace()) { text.trim_end().to_string() } else { text @@ -611,11 +633,11 @@ pub fn into_anthropic( Role::Assistant => anthropic::Role::Assistant, Role::System => unreachable!("System role should never occur here"), }; - if let Some(last_message) = new_messages.last_mut() { - if last_message.role == anthropic_role { - last_message.content.extend(anthropic_message_content); - continue; - } + if let Some(last_message) = new_messages.last_mut() + && last_message.role == anthropic_role + { + last_message.content.extend(anthropic_message_content); + continue; } // Mark the last segment of the message as cached @@ -791,7 +813,7 @@ impl AnthropicEventMapper { ))]; } } - return vec![]; + vec![] } }, Event::ContentBlockStop { index } => { @@ -902,12 +924,18 @@ struct ConfigurationView { api_key_editor: Entity<Editor>, state: gpui::Entity<State>, load_credentials_task: Option<Task<()>>, + target_agent: ConfigurationViewTargetAgent, } impl ConfigurationView { const PLACEHOLDER_TEXT: &'static str = "sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self { + fn new( + state: gpui::Entity<State>, + target_agent: ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut Context<Self>, + ) -> Self { cx.observe(&state, |_, _, cx| { cx.notify(); }) @@ -939,6 +967,7 @@ impl ConfigurationView { }), state, load_credentials_task, + target_agent, } } @@ -1012,7 +1041,10 @@ impl Render for ConfigurationView { v_flex() .size_full() .on_action(cx.listener(Self::save_api_key)) - .child(Label::new("To use Zed's agent with Anthropic, you need to add an API key. Follow these steps:")) + .child(Label::new(format!("To use {}, you need to add an API key. Follow these steps:", match self.target_agent { + ConfigurationViewTargetAgent::ZedAgent => "Zed's agent with Anthropic", + ConfigurationViewTargetAgent::Other(agent) => agent, + }))) .child( List::new() .child( @@ -1023,7 +1055,7 @@ impl Render for ConfigurationView { ) ) .child( - InstructionListItem::text_only("Paste your API key below and hit enter to start using the assistant") + InstructionListItem::text_only("Paste your API key below and hit enter to start using the agent") ) ) .child( diff --git a/crates/language_models/src/provider/bedrock.rs b/crates/language_models/src/provider/bedrock.rs index 6df96c5c56..193d218094 100644 --- a/crates/language_models/src/provider/bedrock.rs +++ b/crates/language_models/src/provider/bedrock.rs @@ -150,7 +150,7 @@ impl State { let credentials_provider = <dyn CredentialsProvider>::global(cx); cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(AMAZON_AWS_URL, &cx) + .delete_credentials(AMAZON_AWS_URL, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -174,7 +174,7 @@ impl State { AMAZON_AWS_URL, "Bearer", &serde_json::to_vec(&credentials)?, - &cx, + cx, ) .await?; this.update(cx, |this, cx| { @@ -206,7 +206,7 @@ impl State { (credentials, true) } else { let (_, credentials) = credentials_provider - .read_credentials(AMAZON_AWS_URL, &cx) + .read_credentials(AMAZON_AWS_URL, cx) .await? .ok_or_else(|| AuthenticateError::CredentialsNotFound)?; ( @@ -348,7 +348,12 @@ impl LanguageModelProvider for BedrockLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } @@ -407,10 +412,10 @@ impl BedrockModel { .region(Region::new(region)) .timeout_config(TimeoutConfig::disabled()); - if let Some(endpoint_url) = endpoint { - if !endpoint_url.is_empty() { - config_builder = config_builder.endpoint_url(endpoint_url); - } + if let Some(endpoint_url) = endpoint + && !endpoint_url.is_empty() + { + config_builder = config_builder.endpoint_url(endpoint_url); } match auth_method { @@ -460,7 +465,7 @@ impl BedrockModel { Result<BoxStream<'static, Result<BedrockStreamingResponse, BedrockError>>>, > { let Ok(runtime_client) = self - .get_or_init_client(&cx) + .get_or_init_client(cx) .cloned() .context("Bedrock client not initialized") else { @@ -723,11 +728,11 @@ pub fn into_bedrock( Role::Assistant => bedrock::BedrockRole::Assistant, Role::System => unreachable!("System role should never occur here"), }; - if let Some(last_message) = new_messages.last_mut() { - if last_message.role == bedrock_role { - last_message.content.extend(bedrock_message_content); - continue; - } + if let Some(last_message) = new_messages.last_mut() + && last_message.role == bedrock_role + { + last_message.content.extend(bedrock_message_content); + continue; } new_messages.push( BedrockMessage::builder() diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index c1337399f9..d3fee7b63b 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -193,7 +193,7 @@ impl State { fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let client = self.client.clone(); cx.spawn(async move |state, cx| { - client.sign_in_with_optional_connect(true, &cx).await?; + client.sign_in_with_optional_connect(true, cx).await?; state.update(cx, |_, cx| cx.notify()) }) } @@ -270,7 +270,7 @@ impl State { if response.status().is_success() { let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; - return Ok(serde_json::from_str(&body)?); + Ok(serde_json::from_str(&body)?) } else { let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; @@ -391,7 +391,12 @@ impl LanguageModelProvider for CloudLanguageModelProvider { Task::ready(Ok(())) } - fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + _: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|_| ConfigurationView::new(self.state.clone())) .into() } @@ -592,15 +597,13 @@ impl CloudLanguageModel { .headers() .get(SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME) .and_then(|resource| resource.to_str().ok()) - { - if let Some(plan) = response + && let Some(plan) = response .headers() .get(CURRENT_PLAN_HEADER_NAME) .and_then(|plan| plan.to_str().ok()) .and_then(|plan| cloud_llm_client::Plan::from_str(plan).ok()) - { - return Err(anyhow!(ModelRequestLimitReachedError { plan })); - } + { + return Err(anyhow!(ModelRequestLimitReachedError { plan })); } } else if status == StatusCode::PAYMENT_REQUIRED { return Err(anyhow!(PaymentRequiredError)); @@ -657,29 +660,29 @@ where impl From<ApiError> for LanguageModelCompletionError { fn from(error: ApiError) -> Self { - if let Ok(cloud_error) = serde_json::from_str::<CloudApiError>(&error.body) { - if cloud_error.code.starts_with("upstream_http_") { - let status = if let Some(status) = cloud_error.upstream_status { - status - } else if cloud_error.code.ends_with("_error") { - error.status - } else { - // If there's a status code in the code string (e.g. "upstream_http_429") - // then use that; otherwise, see if the JSON contains a status code. - cloud_error - .code - .strip_prefix("upstream_http_") - .and_then(|code_str| code_str.parse::<u16>().ok()) - .and_then(|code| StatusCode::from_u16(code).ok()) - .unwrap_or(error.status) - }; + if let Ok(cloud_error) = serde_json::from_str::<CloudApiError>(&error.body) + && cloud_error.code.starts_with("upstream_http_") + { + let status = if let Some(status) = cloud_error.upstream_status { + status + } else if cloud_error.code.ends_with("_error") { + error.status + } else { + // If there's a status code in the code string (e.g. "upstream_http_429") + // then use that; otherwise, see if the JSON contains a status code. + cloud_error + .code + .strip_prefix("upstream_http_") + .and_then(|code_str| code_str.parse::<u16>().ok()) + .and_then(|code| StatusCode::from_u16(code).ok()) + .unwrap_or(error.status) + }; - return LanguageModelCompletionError::UpstreamProviderError { - message: cloud_error.message, - status, - retry_after: cloud_error.retry_after.map(Duration::from_secs_f64), - }; - } + return LanguageModelCompletionError::UpstreamProviderError { + message: cloud_error.message, + status, + retry_after: cloud_error.retry_after.map(Duration::from_secs_f64), + }; } let retry_after = None; diff --git a/crates/language_models/src/provider/copilot_chat.rs b/crates/language_models/src/provider/copilot_chat.rs index 73f73a9a31..eb12c0056f 100644 --- a/crates/language_models/src/provider/copilot_chat.rs +++ b/crates/language_models/src/provider/copilot_chat.rs @@ -176,7 +176,12 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider { Task::ready(Err(err.into())) } - fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + _: &mut Window, + cx: &mut App, + ) -> AnyView { let state = self.state.clone(); cx.new(|cx| ConfigurationView::new(state, cx)).into() } diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index a568ef4034..8c7f8bcc35 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -77,7 +77,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -96,7 +96,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await?; this.update(cx, |this, cx| { this.api_key = Some(api_key); @@ -120,7 +120,7 @@ impl State { (api_key, true) } else { let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) + .read_credentials(&api_url, cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( @@ -229,7 +229,12 @@ impl LanguageModelProvider for DeepSeekLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } diff --git a/crates/language_models/src/provider/google.rs b/crates/language_models/src/provider/google.rs index b287e8181a..a36ce949b1 100644 --- a/crates/language_models/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -110,7 +110,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -129,7 +129,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await?; this.update(cx, |this, cx| { this.api_key = Some(api_key); @@ -156,7 +156,7 @@ impl State { (api_key, true) } else { let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) + .read_credentials(&api_url, cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( @@ -277,7 +277,12 @@ impl LanguageModelProvider for GoogleLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } @@ -525,7 +530,7 @@ pub fn into_google( let system_instructions = if request .messages .first() - .map_or(false, |msg| matches!(msg.role, Role::System)) + .is_some_and(|msg| matches!(msg.role, Role::System)) { let message = request.messages.remove(0); Some(SystemInstruction { diff --git a/crates/language_models/src/provider/lmstudio.rs b/crates/language_models/src/provider/lmstudio.rs index 36a32ab941..7ac08f2c15 100644 --- a/crates/language_models/src/provider/lmstudio.rs +++ b/crates/language_models/src/provider/lmstudio.rs @@ -226,7 +226,12 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, _window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + _window: &mut Window, + cx: &mut App, + ) -> AnyView { let state = self.state.clone(); cx.new(|cx| ConfigurationView::new(state, cx)).into() } diff --git a/crates/language_models/src/provider/mistral.rs b/crates/language_models/src/provider/mistral.rs index 4a0d740334..3f8c2e2a67 100644 --- a/crates/language_models/src/provider/mistral.rs +++ b/crates/language_models/src/provider/mistral.rs @@ -76,7 +76,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -95,7 +95,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await?; this.update(cx, |this, cx| { this.api_key = Some(api_key); @@ -119,7 +119,7 @@ impl State { (api_key, true) } else { let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) + .read_credentials(&api_url, cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( @@ -243,7 +243,12 @@ impl LanguageModelProvider for MistralLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } diff --git a/crates/language_models/src/provider/ollama.rs b/crates/language_models/src/provider/ollama.rs index 0c2b1107b1..93844542ea 100644 --- a/crates/language_models/src/provider/ollama.rs +++ b/crates/language_models/src/provider/ollama.rs @@ -255,7 +255,12 @@ impl LanguageModelProvider for OllamaLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { let state = self.state.clone(); cx.new(|cx| ConfigurationView::new(state, window, cx)) .into() diff --git a/crates/language_models/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs index eaf8d885b3..4348fd4211 100644 --- a/crates/language_models/src/provider/open_ai.rs +++ b/crates/language_models/src/provider/open_ai.rs @@ -75,7 +75,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -94,7 +94,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await .log_err(); this.update(cx, |this, cx| { @@ -119,7 +119,7 @@ impl State { (api_key, true) } else { let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) + .read_credentials(&api_url, cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( @@ -233,7 +233,12 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } @@ -399,7 +404,7 @@ pub fn into_open_ai( match content { MessageContent::Text(text) | MessageContent::Thinking { text, .. } => { add_message_content_part( - open_ai::MessagePart::Text { text: text }, + open_ai::MessagePart::Text { text }, message.role, &mut messages, ) diff --git a/crates/language_models/src/provider/open_ai_compatible.rs b/crates/language_models/src/provider/open_ai_compatible.rs index e2d3adb198..55df534cc9 100644 --- a/crates/language_models/src/provider/open_ai_compatible.rs +++ b/crates/language_models/src/provider/open_ai_compatible.rs @@ -87,7 +87,7 @@ impl State { let api_url = self.settings.api_url.clone(); cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -103,7 +103,7 @@ impl State { let api_url = self.settings.api_url.clone(); cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await .log_err(); this.update(cx, |this, cx| { @@ -126,7 +126,7 @@ impl State { (api_key, true) } else { let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) + .read_credentials(&api_url, cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( @@ -243,7 +243,12 @@ impl LanguageModelProvider for OpenAiCompatibleLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } diff --git a/crates/language_models/src/provider/open_router.rs b/crates/language_models/src/provider/open_router.rs index 3a492086f1..8f2abfce35 100644 --- a/crates/language_models/src/provider/open_router.rs +++ b/crates/language_models/src/provider/open_router.rs @@ -112,7 +112,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -131,7 +131,7 @@ impl State { .clone(); cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await .log_err(); this.update(cx, |this, cx| { @@ -157,7 +157,7 @@ impl State { (api_key, true) } else { let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) + .read_credentials(&api_url, cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( @@ -306,7 +306,12 @@ impl LanguageModelProvider for OpenRouterLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } diff --git a/crates/language_models/src/provider/vercel.rs b/crates/language_models/src/provider/vercel.rs index 9f447cb68b..84f3175d1e 100644 --- a/crates/language_models/src/provider/vercel.rs +++ b/crates/language_models/src/provider/vercel.rs @@ -71,7 +71,7 @@ impl State { }; cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -92,7 +92,7 @@ impl State { }; cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await .log_err(); this.update(cx, |this, cx| { @@ -119,7 +119,7 @@ impl State { (api_key, true) } else { let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) + .read_credentials(&api_url, cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( @@ -230,7 +230,12 @@ impl LanguageModelProvider for VercelLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } diff --git a/crates/language_models/src/provider/x_ai.rs b/crates/language_models/src/provider/x_ai.rs index fed6fe92bf..b37a55e19f 100644 --- a/crates/language_models/src/provider/x_ai.rs +++ b/crates/language_models/src/provider/x_ai.rs @@ -71,7 +71,7 @@ impl State { }; cx.spawn(async move |this, cx| { credentials_provider - .delete_credentials(&api_url, &cx) + .delete_credentials(&api_url, cx) .await .log_err(); this.update(cx, |this, cx| { @@ -92,7 +92,7 @@ impl State { }; cx.spawn(async move |this, cx| { credentials_provider - .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) + .write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx) .await .log_err(); this.update(cx, |this, cx| { @@ -119,7 +119,7 @@ impl State { (api_key, true) } else { let (_, api_key) = credentials_provider - .read_credentials(&api_url, &cx) + .read_credentials(&api_url, cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( @@ -230,7 +230,12 @@ impl LanguageModelProvider for XAiLanguageModelProvider { self.state.update(cx, |state, cx| state.authenticate(cx)) } - fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { + fn configuration_view( + &self, + _target_agent: language_model::ConfigurationViewTargetAgent, + window: &mut Window, + cx: &mut App, + ) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs index c5c5eceab5..56924c4cd2 100644 --- a/crates/language_selector/src/active_buffer_language.rs +++ b/crates/language_selector/src/active_buffer_language.rs @@ -28,10 +28,10 @@ impl ActiveBufferLanguage { self.active_language = Some(None); let editor = editor.read(cx); - if let Some((_, buffer, _)) = editor.active_excerpt(cx) { - if let Some(language) = buffer.read(cx).language() { - self.active_language = Some(Some(language.name())); - } + if let Some((_, buffer, _)) = editor.active_excerpt(cx) + && let Some(language) = buffer.read(cx).language() + { + self.active_language = Some(Some(language.name())); } cx.notify(); diff --git a/crates/language_tools/src/key_context_view.rs b/crates/language_tools/src/key_context_view.rs index 88131781ec..057259d114 100644 --- a/crates/language_tools/src/key_context_view.rs +++ b/crates/language_tools/src/key_context_view.rs @@ -71,12 +71,10 @@ impl KeyContextView { } else { None } + } else if this.action_matches(&e.action, binding.action()) { + Some(true) } else { - if this.action_matches(&e.action, binding.action()) { - Some(true) - } else { - Some(false) - } + Some(false) }; let predicate = if let Some(predicate) = binding.predicate() { format!("{}", predicate) @@ -98,9 +96,7 @@ impl KeyContextView { cx.notify(); }); let sub2 = cx.observe_pending_input(window, |this, window, cx| { - this.pending_keystrokes = window - .pending_input_keystrokes() - .map(|k| k.iter().cloned().collect()); + this.pending_keystrokes = window.pending_input_keystrokes().map(|k| k.to_vec()); if this.pending_keystrokes.is_some() { this.last_keystrokes.take(); } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 823d59ce12..3285efaaef 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -254,35 +254,35 @@ impl LogStore { let copilot_subscription = Copilot::global(cx).map(|copilot| { let copilot = &copilot; cx.subscribe(copilot, |this, copilot, edit_prediction_event, cx| { - if let copilot::Event::CopilotLanguageServerStarted = edit_prediction_event { - if let Some(server) = copilot.read(cx).language_server() { - let server_id = server.server_id(); - let weak_this = cx.weak_entity(); - this.copilot_log_subscription = - Some(server.on_notification::<copilot::request::LogMessage, _>( - move |params, cx| { - weak_this - .update(cx, |this, cx| { - this.add_language_server_log( - server_id, - MessageType::LOG, - ¶ms.message, - cx, - ); - }) - .ok(); - }, - )); - let name = LanguageServerName::new_static("copilot"); - this.add_language_server( - LanguageServerKind::Global, - server.server_id(), - Some(name), - None, - Some(server.clone()), - cx, - ); - } + if let copilot::Event::CopilotLanguageServerStarted = edit_prediction_event + && let Some(server) = copilot.read(cx).language_server() + { + let server_id = server.server_id(); + let weak_this = cx.weak_entity(); + this.copilot_log_subscription = + Some(server.on_notification::<copilot::request::LogMessage, _>( + move |params, cx| { + weak_this + .update(cx, |this, cx| { + this.add_language_server_log( + server_id, + MessageType::LOG, + ¶ms.message, + cx, + ); + }) + .ok(); + }, + )); + let name = LanguageServerName::new_static("copilot"); + this.add_language_server( + LanguageServerKind::Global, + server.server_id(), + Some(name), + None, + Some(server.clone()), + cx, + ); } }) }); @@ -661,7 +661,7 @@ impl LogStore { IoKind::StdOut => true, IoKind::StdIn => false, IoKind::StdErr => { - self.add_language_server_log(language_server_id, MessageType::LOG, &message, cx); + self.add_language_server_log(language_server_id, MessageType::LOG, message, cx); return Some(()); } }; @@ -733,16 +733,14 @@ impl LspLogView { let first_server_id_for_project = store.read(cx).server_ids_for_project(&weak_project).next(); if let Some(current_lsp) = this.current_server_id { - if !store.read(cx).language_servers.contains_key(¤t_lsp) { - if let Some(server_id) = first_server_id_for_project { - match this.active_entry_kind { - LogKind::Rpc => { - this.show_rpc_trace_for_server(server_id, window, cx) - } - LogKind::Trace => this.show_trace_for_server(server_id, window, cx), - LogKind::Logs => this.show_logs_for_server(server_id, window, cx), - LogKind::ServerInfo => this.show_server_info(server_id, window, cx), - } + if !store.read(cx).language_servers.contains_key(¤t_lsp) + && let Some(server_id) = first_server_id_for_project + { + match this.active_entry_kind { + LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx), + LogKind::Trace => this.show_trace_for_server(server_id, window, cx), + LogKind::Logs => this.show_logs_for_server(server_id, window, cx), + LogKind::ServerInfo => this.show_server_info(server_id, window, cx), } } } else if let Some(server_id) = first_server_id_for_project { @@ -776,21 +774,17 @@ impl LspLogView { ], cx, ); - if text.len() > 1024 { - if let Some((fold_offset, _)) = + if text.len() > 1024 + && let Some((fold_offset, _)) = text.char_indices().dropping(1024).next() - { - if fold_offset < text.len() { - editor.fold_ranges( - vec![ - last_offset + fold_offset..last_offset + text.len(), - ], - false, - window, - cx, - ); - } - } + && fold_offset < text.len() + { + editor.fold_ranges( + vec![last_offset + fold_offset..last_offset + text.len()], + false, + window, + cx, + ); } if newest_cursor_is_at_end { @@ -1311,14 +1305,14 @@ impl ToolbarItemView for LspLogToolbarItemView { _: &mut Window, cx: &mut Context<Self>, ) -> workspace::ToolbarItemLocation { - if let Some(item) = active_pane_item { - if let Some(log_view) = item.downcast::<LspLogView>() { - self.log_view = Some(log_view.clone()); - self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| { - cx.notify(); - })); - return ToolbarItemLocation::PrimaryLeft; - } + if let Some(item) = active_pane_item + && let Some(log_view) = item.downcast::<LspLogView>() + { + self.log_view = Some(log_view.clone()); + self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| { + cx.notify(); + })); + return ToolbarItemLocation::PrimaryLeft; } self.log_view = None; self._log_view_subscription = None; diff --git a/crates/language_tools/src/lsp_tool.rs b/crates/language_tools/src/lsp_tool.rs index 3244350a34..dd3e80212f 100644 --- a/crates/language_tools/src/lsp_tool.rs +++ b/crates/language_tools/src/lsp_tool.rs @@ -349,7 +349,6 @@ impl LanguageServerState { .detach(); } else { cx.propagate(); - return; } } }, @@ -523,7 +522,6 @@ impl LspTool { if ProjectSettings::get_global(cx).global_lsp_settings.button { if lsp_tool.lsp_menu.is_none() { lsp_tool.refresh_lsp_menu(true, window, cx); - return; } } else if lsp_tool.lsp_menu.take().is_some() { cx.notify(); diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 9946442ec8..4fe8e11f94 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -103,12 +103,11 @@ impl SyntaxTreeView { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(item) = active_item { - if item.item_id() != cx.entity_id() { - if let Some(editor) = item.act_as::<Editor>(cx) { - self.set_editor(editor, window, cx); - } - } + if let Some(item) = active_item + && item.item_id() != cx.entity_id() + && let Some(editor) = item.act_as::<Editor>(cx) + { + self.set_editor(editor, window, cx); } } @@ -537,12 +536,12 @@ impl ToolbarItemView for SyntaxTreeToolbarItemView { window: &mut Window, cx: &mut Context<Self>, ) -> ToolbarItemLocation { - if let Some(item) = active_pane_item { - if let Some(view) = item.downcast::<SyntaxTreeView>() { - self.tree_view = Some(view.clone()); - self.subscription = Some(cx.observe_in(&view, window, |_, _, _, cx| cx.notify())); - return ToolbarItemLocation::PrimaryLeft; - } + if let Some(item) = active_pane_item + && let Some(view) = item.downcast::<SyntaxTreeView>() + { + self.tree_view = Some(view.clone()); + self.subscription = Some(cx.observe_in(&view, window, |_, _, _, cx| cx.notify())); + return ToolbarItemLocation::PrimaryLeft; } self.tree_view = None; self.subscription = None; diff --git a/crates/languages/src/css.rs b/crates/languages/src/css.rs index a1a5418220..2480d40268 100644 --- a/crates/languages/src/css.rs +++ b/crates/languages/src/css.rs @@ -106,7 +106,7 @@ impl LspAdapter for CssLspAdapter { .should_install_npm_package( Self::PACKAGE_NAME, &server_path, - &container_dir, + container_dir, VersionStrategy::Latest(version), ) .await; diff --git a/crates/languages/src/github_download.rs b/crates/languages/src/github_download.rs index 5b0f1d0729..766c894fbb 100644 --- a/crates/languages/src/github_download.rs +++ b/crates/languages/src/github_download.rs @@ -96,7 +96,7 @@ async fn stream_response_archive( AssetKind::TarGz => extract_tar_gz(destination_path, url, response).await?, AssetKind::Gz => extract_gz(destination_path, url, response).await?, AssetKind::Zip => { - util::archive::extract_zip(&destination_path, response).await?; + util::archive::extract_zip(destination_path, response).await?; } }; Ok(()) @@ -113,11 +113,11 @@ async fn stream_file_archive( AssetKind::Gz => extract_gz(destination_path, url, file_archive).await?, #[cfg(not(windows))] AssetKind::Zip => { - util::archive::extract_seekable_zip(&destination_path, file_archive).await?; + util::archive::extract_seekable_zip(destination_path, file_archive).await?; } #[cfg(windows)] AssetKind::Zip => { - util::archive::extract_zip(&destination_path, file_archive).await?; + util::archive::extract_zip(destination_path, file_archive).await?; } }; Ok(()) diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index f739c5c4c6..d6f9538ee4 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -131,19 +131,19 @@ impl super::LspAdapter for GoLspAdapter { if let Some(version) = *version { let binary_path = container_dir.join(format!("gopls_{version}_go_{go_version}")); - if let Ok(metadata) = fs::metadata(&binary_path).await { - if metadata.is_file() { - remove_matching(&container_dir, |entry| { - entry != binary_path && entry.file_name() != Some(OsStr::new("gobin")) - }) - .await; + if let Ok(metadata) = fs::metadata(&binary_path).await + && metadata.is_file() + { + remove_matching(&container_dir, |entry| { + entry != binary_path && entry.file_name() != Some(OsStr::new("gobin")) + }) + .await; - return Ok(LanguageServerBinary { - path: binary_path.to_path_buf(), - arguments: server_binary_arguments(), - env: None, - }); - } + return Ok(LanguageServerBinary { + path: binary_path.to_path_buf(), + arguments: server_binary_arguments(), + env: None, + }); } } else if let Some(path) = this .cached_server_binary(container_dir.clone(), delegate) @@ -452,7 +452,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ && entry .file_name() .to_str() - .map_or(false, |name| name.starts_with("gopls_")) + .is_some_and(|name| name.starts_with("gopls_")) { last_binary_path = Some(entry.path()); } diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 4db48c67f0..ac653d5b2e 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -234,7 +234,7 @@ impl JsonLspAdapter { schemas .as_array_mut() .unwrap() - .extend(cx.all_action_names().into_iter().map(|&name| { + .extend(cx.all_action_names().iter().map(|&name| { project::lsp_store::json_language_server_ext::url_schema_for_action(name) })); @@ -280,7 +280,7 @@ impl JsonLspAdapter { ) })?; writer.replace(config.clone()); - return Ok(config); + Ok(config) } } @@ -343,7 +343,7 @@ impl LspAdapter for JsonLspAdapter { .should_install_npm_package( Self::PACKAGE_NAME, &server_path, - &container_dir, + container_dir, VersionStrategy::Latest(version), ) .await; diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index e446f22713..75289dd59d 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -244,11 +244,8 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) { cx.observe_flag::<BasedPyrightFeatureFlag, _>({ let languages = languages.clone(); move |enabled, _| { - if enabled { - if let Some(adapter) = basedpyright_lsp_adapter.take() { - languages - .register_available_lsp_adapter(adapter.name(), move || adapter.clone()); - } + if enabled && let Some(adapter) = basedpyright_lsp_adapter.take() { + languages.register_available_lsp_adapter(adapter.name(), move || adapter.clone()); } } }) diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 222e3f1946..6c92d78525 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -204,7 +204,7 @@ impl LspAdapter for PythonLspAdapter { .should_install_npm_package( Self::SERVER_NAME.as_ref(), &server_path, - &container_dir, + container_dir, VersionStrategy::Latest(version), ) .await; @@ -338,31 +338,31 @@ impl LspAdapter for PythonLspAdapter { let interpreter_path = toolchain.path.to_string(); // Detect if this is a virtual environment - if let Some(interpreter_dir) = Path::new(&interpreter_path).parent() { - if let Some(venv_dir) = interpreter_dir.parent() { - // Check if this looks like a virtual environment - if venv_dir.join("pyvenv.cfg").exists() - || venv_dir.join("bin/activate").exists() - || venv_dir.join("Scripts/activate.bat").exists() - { - // Set venvPath and venv at the root level - // This matches the format of a pyrightconfig.json file - if let Some(parent) = venv_dir.parent() { - // Use relative path if the venv is inside the workspace - let venv_path = if parent == adapter.worktree_root_path() { - ".".to_string() - } else { - parent.to_string_lossy().into_owned() - }; - object.insert("venvPath".to_string(), Value::String(venv_path)); - } + if let Some(interpreter_dir) = Path::new(&interpreter_path).parent() + && let Some(venv_dir) = interpreter_dir.parent() + { + // Check if this looks like a virtual environment + if venv_dir.join("pyvenv.cfg").exists() + || venv_dir.join("bin/activate").exists() + || venv_dir.join("Scripts/activate.bat").exists() + { + // Set venvPath and venv at the root level + // This matches the format of a pyrightconfig.json file + if let Some(parent) = venv_dir.parent() { + // Use relative path if the venv is inside the workspace + let venv_path = if parent == adapter.worktree_root_path() { + ".".to_string() + } else { + parent.to_string_lossy().into_owned() + }; + object.insert("venvPath".to_string(), Value::String(venv_path)); + } - if let Some(venv_name) = venv_dir.file_name() { - object.insert( - "venv".to_owned(), - Value::String(venv_name.to_string_lossy().into_owned()), - ); - } + if let Some(venv_name) = venv_dir.file_name() { + object.insert( + "venv".to_owned(), + Value::String(venv_name.to_string_lossy().into_owned()), + ); } } } @@ -711,7 +711,7 @@ impl Default for PythonToolchainProvider { } } -static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[ +static ENV_PRIORITY_LIST: &[PythonEnvironmentKind] = &[ // Prioritize non-Conda environments. PythonEnvironmentKind::Poetry, PythonEnvironmentKind::Pipenv, @@ -828,7 +828,7 @@ impl ToolchainLister for PythonToolchainProvider { .get_env_var("CONDA_PREFIX".to_string()) .map(|conda_prefix| { let is_match = |exe: &Option<PathBuf>| { - exe.as_ref().map_or(false, |e| e.starts_with(&conda_prefix)) + exe.as_ref().is_some_and(|e| e.starts_with(&conda_prefix)) }; match (is_match(&lhs.executable), is_match(&rhs.executable)) { (true, false) => Ordering::Less, @@ -1519,31 +1519,31 @@ impl LspAdapter for BasedPyrightLspAdapter { let interpreter_path = toolchain.path.to_string(); // Detect if this is a virtual environment - if let Some(interpreter_dir) = Path::new(&interpreter_path).parent() { - if let Some(venv_dir) = interpreter_dir.parent() { - // Check if this looks like a virtual environment - if venv_dir.join("pyvenv.cfg").exists() - || venv_dir.join("bin/activate").exists() - || venv_dir.join("Scripts/activate.bat").exists() - { - // Set venvPath and venv at the root level - // This matches the format of a pyrightconfig.json file - if let Some(parent) = venv_dir.parent() { - // Use relative path if the venv is inside the workspace - let venv_path = if parent == adapter.worktree_root_path() { - ".".to_string() - } else { - parent.to_string_lossy().into_owned() - }; - object.insert("venvPath".to_string(), Value::String(venv_path)); - } + if let Some(interpreter_dir) = Path::new(&interpreter_path).parent() + && let Some(venv_dir) = interpreter_dir.parent() + { + // Check if this looks like a virtual environment + if venv_dir.join("pyvenv.cfg").exists() + || venv_dir.join("bin/activate").exists() + || venv_dir.join("Scripts/activate.bat").exists() + { + // Set venvPath and venv at the root level + // This matches the format of a pyrightconfig.json file + if let Some(parent) = venv_dir.parent() { + // Use relative path if the venv is inside the workspace + let venv_path = if parent == adapter.worktree_root_path() { + ".".to_string() + } else { + parent.to_string_lossy().into_owned() + }; + object.insert("venvPath".to_string(), Value::String(venv_path)); + } - if let Some(venv_name) = venv_dir.file_name() { - object.insert( - "venv".to_owned(), - Value::String(venv_name.to_string_lossy().into_owned()), - ); - } + if let Some(venv_name) = venv_dir.file_name() { + object.insert( + "venv".to_owned(), + Value::String(venv_name.to_string_lossy().into_owned()), + ); } } } diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 3ef7c1ba34..eb5e0cee7c 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -403,7 +403,7 @@ impl LspAdapter for RustLspAdapter { } else if completion .detail .as_ref() - .map_or(false, |detail| detail.starts_with("macro_rules! ")) + .is_some_and(|detail| detail.starts_with("macro_rules! ")) { let text = completion.label.clone(); let len = text.len(); @@ -496,7 +496,7 @@ impl LspAdapter for RustLspAdapter { let enable_lsp_tasks = ProjectSettings::get_global(cx) .lsp .get(&SERVER_NAME) - .map_or(false, |s| s.enable_lsp_tasks); + .is_some_and(|s| s.enable_lsp_tasks); if enable_lsp_tasks { let experimental = json!({ "runnables": { @@ -581,7 +581,7 @@ impl ContextProvider for RustContextProvider { if let (Some(path), Some(stem)) = (&local_abs_path, task_variables.get(&VariableName::Stem)) { - let fragment = test_fragment(&variables, &path, stem); + let fragment = test_fragment(&variables, path, stem); variables.insert(RUST_TEST_FRAGMENT_TASK_VARIABLE, fragment); }; if let Some(test_name) = @@ -598,16 +598,14 @@ impl ContextProvider for RustContextProvider { if let Some(path) = local_abs_path .as_deref() .and_then(|local_abs_path| local_abs_path.parent()) - { - if let Some(package_name) = + && let Some(package_name) = human_readable_package_name(path, project_env.as_ref()).await - { - variables.insert(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name); - } + { + variables.insert(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name); } if let Some(path) = local_abs_path.as_ref() && let Some((target, manifest_path)) = - target_info_from_abs_path(&path, project_env.as_ref()).await + target_info_from_abs_path(path, project_env.as_ref()).await { if let Some(target) = target { variables.extend(TaskVariables::from_iter([ @@ -1570,7 +1568,7 @@ mod tests { let found = test_fragment( &TaskVariables::from_iter(variables.into_iter().map(|(k, v)| (k, v.to_owned()))), path, - &path.file_stem().unwrap().to_str().unwrap(), + path.file_stem().unwrap().to_str().unwrap(), ); assert_eq!(expected, found); } diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs index 27939c645c..29a96d9515 100644 --- a/crates/languages/src/tailwind.rs +++ b/crates/languages/src/tailwind.rs @@ -111,7 +111,7 @@ impl LspAdapter for TailwindLspAdapter { .should_install_npm_package( Self::PACKAGE_NAME, &server_path, - &container_dir, + container_dir, VersionStrategy::Latest(version), ) .await; diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index dec7df4060..afc84c3aff 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -341,10 +341,10 @@ async fn detect_package_manager( fs: Arc<dyn Fs>, package_json_data: Option<PackageJsonData>, ) -> &'static str { - if let Some(package_json_data) = package_json_data { - if let Some(package_manager) = package_json_data.package_manager { - return package_manager; - } + if let Some(package_json_data) = package_json_data + && let Some(package_manager) = package_json_data.package_manager + { + return package_manager; } if fs.is_file(&worktree_root.join("pnpm-lock.yaml")).await { return "pnpm"; @@ -587,7 +587,7 @@ impl LspAdapter for TypeScriptLspAdapter { .should_install_npm_package( Self::PACKAGE_NAME, &server_path, - &container_dir, + container_dir, VersionStrategy::Latest(version.typescript_version.as_str()), ) .await; @@ -910,7 +910,7 @@ impl LspAdapter for EsLintLspAdapter { let server_path = destination_path.join(Self::SERVER_PATH); if fs::metadata(&server_path).await.is_err() { - remove_matching(&container_dir, |entry| entry != destination_path).await; + remove_matching(&container_dir, |_| true).await; download_server_binary( delegate, diff --git a/crates/languages/src/yaml.rs b/crates/languages/src/yaml.rs index 137a9c2282..6ac92e0b2b 100644 --- a/crates/languages/src/yaml.rs +++ b/crates/languages/src/yaml.rs @@ -105,7 +105,7 @@ impl LspAdapter for YamlLspAdapter { .should_install_npm_package( Self::PACKAGE_NAME, &server_path, - &container_dir, + container_dir, VersionStrategy::Latest(version), ) .await; diff --git a/crates/livekit_client/examples/test_app.rs b/crates/livekit_client/examples/test_app.rs index e1d01df534..51f335c2db 100644 --- a/crates/livekit_client/examples/test_app.rs +++ b/crates/livekit_client/examples/test_app.rs @@ -159,14 +159,14 @@ impl LivekitWindow { if output .audio_output_stream .as_ref() - .map_or(false, |(track, _)| track.sid() == unpublish_sid) + .is_some_and(|(track, _)| track.sid() == unpublish_sid) { output.audio_output_stream.take(); } if output .screen_share_output_view .as_ref() - .map_or(false, |(track, _)| track.sid() == unpublish_sid) + .is_some_and(|(track, _)| track.sid() == unpublish_sid) { output.screen_share_output_view.take(); } diff --git a/crates/livekit_client/src/test.rs b/crates/livekit_client/src/test.rs index e02c4d876f..873e0222d0 100644 --- a/crates/livekit_client/src/test.rs +++ b/crates/livekit_client/src/test.rs @@ -421,7 +421,7 @@ impl TestServer { track_sid: &TrackSid, muted: bool, ) -> Result<()> { - let claims = livekit_api::token::validate(&token, &self.secret_key)?; + let claims = livekit_api::token::validate(token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); let mut server_rooms = self.rooms.lock(); @@ -475,7 +475,7 @@ impl TestServer { } pub(crate) fn is_track_muted(&self, token: &str, track_sid: &TrackSid) -> Option<bool> { - let claims = livekit_api::token::validate(&token, &self.secret_key).ok()?; + let claims = livekit_api::token::validate(token, &self.secret_key).ok()?; let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); @@ -736,14 +736,14 @@ impl Room { impl Drop for RoomState { fn drop(&mut self) { - if self.connection_state == ConnectionState::Connected { - if let Ok(server) = TestServer::get(&self.url) { - let executor = server.executor.clone(); - let token = self.token.clone(); - executor - .spawn(async move { server.leave_room(token).await.ok() }) - .detach(); - } + if self.connection_state == ConnectionState::Connected + && let Ok(server) = TestServer::get(&self.url) + { + let executor = server.executor.clone(); + let token = self.token.clone(); + executor + .spawn(async move { server.leave_room(token).await.ok() }) + .detach(); } } } diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index a3235a9773..a161ddd074 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -340,27 +340,26 @@ impl Markdown { } for (range, event) in &events { - if let MarkdownEvent::Start(MarkdownTag::Image { dest_url, .. }) = event { - if let Some(data_url) = dest_url.strip_prefix("data:") { - let Some((mime_info, data)) = data_url.split_once(',') else { - continue; - }; - let Some((mime_type, encoding)) = mime_info.split_once(';') else { - continue; - }; - let Some(format) = ImageFormat::from_mime_type(mime_type) else { - continue; - }; - let is_base64 = encoding == "base64"; - if is_base64 { - if let Some(bytes) = base64::prelude::BASE64_STANDARD - .decode(data) - .log_with_level(Level::Debug) - { - let image = Arc::new(Image::from_bytes(format, bytes)); - images_by_source_offset.insert(range.start, image); - } - } + if let MarkdownEvent::Start(MarkdownTag::Image { dest_url, .. }) = event + && let Some(data_url) = dest_url.strip_prefix("data:") + { + let Some((mime_info, data)) = data_url.split_once(',') else { + continue; + }; + let Some((mime_type, encoding)) = mime_info.split_once(';') else { + continue; + }; + let Some(format) = ImageFormat::from_mime_type(mime_type) else { + continue; + }; + let is_base64 = encoding == "base64"; + if is_base64 + && let Some(bytes) = base64::prelude::BASE64_STANDARD + .decode(data) + .log_with_level(Level::Debug) + { + let image = Arc::new(Image::from_bytes(format, bytes)); + images_by_source_offset.insert(range.start, image); } } } @@ -659,13 +658,13 @@ impl MarkdownElement { let rendered_text = rendered_text.clone(); move |markdown, event: &MouseUpEvent, phase, window, cx| { if phase.bubble() { - if let Some(pressed_link) = markdown.pressed_link.take() { - if Some(&pressed_link) == rendered_text.link_for_position(event.position) { - if let Some(open_url) = on_open_url.as_ref() { - open_url(pressed_link.destination_url, window, cx); - } else { - cx.open_url(&pressed_link.destination_url); - } + if let Some(pressed_link) = markdown.pressed_link.take() + && Some(&pressed_link) == rendered_text.link_for_position(event.position) + { + if let Some(open_url) = on_open_url.as_ref() { + open_url(pressed_link.destination_url, window, cx); + } else { + cx.open_url(&pressed_link.destination_url); } } } else if markdown.selection.pending { @@ -758,10 +757,10 @@ impl Element for MarkdownElement { let mut current_img_block_range: Option<Range<usize>> = None; for (range, event) in parsed_markdown.events.iter() { // Skip alt text for images that rendered - if let Some(current_img_block_range) = ¤t_img_block_range { - if current_img_block_range.end > range.end { - continue; - } + if let Some(current_img_block_range) = ¤t_img_block_range + && current_img_block_range.end > range.end + { + continue; } match event { @@ -875,7 +874,7 @@ impl Element for MarkdownElement { (CodeBlockRenderer::Custom { render, .. }, _) => { let parent_container = render( kind, - &parsed_markdown, + parsed_markdown, range.clone(), metadata.clone(), window, @@ -1085,7 +1084,13 @@ impl Element for MarkdownElement { cx, ); el.child( - div().absolute().top_1().right_0p5().w_5().child(codeblock), + h_flex() + .w_5() + .absolute() + .top_1() + .right_1() + .justify_center() + .child(codeblock), ) }); } @@ -1696,10 +1701,10 @@ impl RenderedText { while let Some(line) = lines.next() { let line_bounds = line.layout.bounds(); if position.y > line_bounds.bottom() { - if let Some(next_line) = lines.peek() { - if position.y < next_line.layout.bounds().top() { - return Err(line.source_end); - } + if let Some(next_line) = lines.peek() + && position.y < next_line.layout.bounds().top() + { + return Err(line.source_end); } continue; diff --git a/crates/markdown/src/parser.rs b/crates/markdown/src/parser.rs index 1035335ccb..3720e5b1ef 100644 --- a/crates/markdown/src/parser.rs +++ b/crates/markdown/src/parser.rs @@ -247,7 +247,7 @@ pub fn parse_markdown( events.push(event_for( text, range.source_range.start..range.source_range.start + prefix_len, - &head, + head, )); range.parsed = CowStr::Boxed(tail.into()); range.merged_range.start += prefix_len; diff --git a/crates/markdown_preview/src/markdown_parser.rs b/crates/markdown_preview/src/markdown_parser.rs index 27691f2ecf..8c8d9e177f 100644 --- a/crates/markdown_preview/src/markdown_parser.rs +++ b/crates/markdown_preview/src/markdown_parser.rs @@ -76,22 +76,22 @@ impl<'a> MarkdownParser<'a> { if self.eof() || (steps + self.cursor) >= self.tokens.len() { return self.tokens.last(); } - return self.tokens.get(self.cursor + steps); + self.tokens.get(self.cursor + steps) } fn previous(&self) -> Option<&(Event<'_>, Range<usize>)> { if self.cursor == 0 || self.cursor > self.tokens.len() { return None; } - return self.tokens.get(self.cursor - 1); + self.tokens.get(self.cursor - 1) } fn current(&self) -> Option<&(Event<'_>, Range<usize>)> { - return self.peek(0); + self.peek(0) } fn current_event(&self) -> Option<&Event<'_>> { - return self.current().map(|(event, _)| event); + self.current().map(|(event, _)| event) } fn is_text_like(event: &Event) -> bool { @@ -300,13 +300,12 @@ impl<'a> MarkdownParser<'a> { if style != MarkdownHighlightStyle::default() && last_run_len < text.len() { let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { - if last_range.end == last_run_len - && last_style == &MarkdownHighlight::Style(style.clone()) - { - last_range.end = text.len(); - new_highlight = false; - } + if let Some((last_range, last_style)) = highlights.last_mut() + && last_range.end == last_run_len + && last_style == &MarkdownHighlight::Style(style.clone()) + { + last_range.end = text.len(); + new_highlight = false; } if new_highlight { highlights.push(( @@ -579,10 +578,10 @@ impl<'a> MarkdownParser<'a> { } } else { let block = self.parse_block().await; - if let Some(block) = block { - if let Some(list_item) = items_stack.last_mut() { - list_item.content.extend(block); - } + if let Some(block) = block + && let Some(list_item) = items_stack.last_mut() + { + list_item.content.extend(block); } } } diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index a0c8819991..c2b98f69c8 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -151,10 +151,9 @@ impl MarkdownPreviewView { if let Some(editor) = workspace .active_item(cx) .and_then(|item| item.act_as::<Editor>(cx)) + && Self::is_markdown_file(&editor, cx) { - if Self::is_markdown_file(&editor, cx) { - return Some(editor); - } + return Some(editor); } None } @@ -243,32 +242,30 @@ impl MarkdownPreviewView { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(item) = active_item { - if item.item_id() != cx.entity_id() { - if let Some(editor) = item.act_as::<Editor>(cx) { - if Self::is_markdown_file(&editor, cx) { - self.set_editor(editor, window, cx); - } - } - } + if let Some(item) = active_item + && item.item_id() != cx.entity_id() + && let Some(editor) = item.act_as::<Editor>(cx) + && Self::is_markdown_file(&editor, cx) + { + self.set_editor(editor, window, cx); } } pub fn is_markdown_file<V>(editor: &Entity<Editor>, cx: &mut Context<V>) -> bool { let buffer = editor.read(cx).buffer().read(cx); - if let Some(buffer) = buffer.as_singleton() { - if let Some(language) = buffer.read(cx).language() { - return language.name() == "Markdown".into(); - } + if let Some(buffer) = buffer.as_singleton() + && let Some(language) = buffer.read(cx).language() + { + return language.name() == "Markdown".into(); } false } fn set_editor(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut Context<Self>) { - if let Some(active) = &self.active_editor { - if active.editor == editor { - return; - } + if let Some(active) = &self.active_editor + && active.editor == editor + { + return; } let subscription = cx.subscribe_in( @@ -552,21 +549,20 @@ impl Render for MarkdownPreviewView { .group("markdown-block") .on_click(cx.listener( move |this, event: &ClickEvent, window, cx| { - if event.click_count() == 2 { - if let Some(source_range) = this + if event.click_count() == 2 + && let Some(source_range) = this .contents .as_ref() .and_then(|c| c.children.get(ix)) .and_then(|block: &ParsedMarkdownElement| { block.source_range() }) - { - this.move_cursor_to_block( - window, - cx, - source_range.start..source_range.start, - ); - } + { + this.move_cursor_to_block( + window, + cx, + source_range.start..source_range.start, + ); } }, )) diff --git a/crates/markdown_preview/src/markdown_renderer.rs b/crates/markdown_preview/src/markdown_renderer.rs index 37d2ca2110..b0b10e927c 100644 --- a/crates/markdown_preview/src/markdown_renderer.rs +++ b/crates/markdown_preview/src/markdown_renderer.rs @@ -111,11 +111,10 @@ impl RenderContext { /// buffer font size changes. The callees of this function should be reimplemented to use real /// relative sizing once that is implemented in GPUI pub fn scaled_rems(&self, rems: f32) -> Rems { - return self - .buffer_text_style + self.buffer_text_style .font_size .to_rems(self.window_rem_size) - .mul(rems); + .mul(rems) } /// This ensures that children inside of block quotes @@ -459,13 +458,13 @@ fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) - let mut max_lengths: Vec<usize> = vec![0; parsed.header.children.len()]; for (index, cell) in parsed.header.children.iter().enumerate() { - let length = paragraph_len(&cell); + let length = paragraph_len(cell); max_lengths[index] = length; } for row in &parsed.body { for (index, cell) in row.children.iter().enumerate() { - let length = paragraph_len(&cell); + let length = paragraph_len(cell); if length > max_lengths[index] { max_lengths[index] = length; diff --git a/crates/migrator/src/migrations/m_2025_01_29/keymap.rs b/crates/migrator/src/migrations/m_2025_01_29/keymap.rs index 646af8f63d..c32da88229 100644 --- a/crates/migrator/src/migrations/m_2025_01_29/keymap.rs +++ b/crates/migrator/src/migrations/m_2025_01_29/keymap.rs @@ -242,22 +242,22 @@ static STRING_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| { "inline_completion::ToggleMenu", "edit_prediction::ToggleMenu", ), - ("editor::NextEditPrediction", "editor::NextEditPrediction"), + ("editor::NextInlineCompletion", "editor::NextEditPrediction"), ( - "editor::PreviousEditPrediction", + "editor::PreviousInlineCompletion", "editor::PreviousEditPrediction", ), ( - "editor::AcceptPartialEditPrediction", + "editor::AcceptPartialInlineCompletion", "editor::AcceptPartialEditPrediction", ), - ("editor::ShowEditPrediction", "editor::ShowEditPrediction"), + ("editor::ShowInlineCompletion", "editor::ShowEditPrediction"), ( - "editor::AcceptEditPrediction", + "editor::AcceptInlineCompletion", "editor::AcceptEditPrediction", ), ( - "editor::ToggleEditPredictions", + "editor::ToggleInlineCompletions", "editor::ToggleEditPrediction", ), ]) diff --git a/crates/migrator/src/migrations/m_2025_05_05/settings.rs b/crates/migrator/src/migrations/m_2025_05_05/settings.rs index 88c6c338d1..77da1b9a07 100644 --- a/crates/migrator/src/migrations/m_2025_05_05/settings.rs +++ b/crates/migrator/src/migrations/m_2025_05_05/settings.rs @@ -24,7 +24,7 @@ fn rename_assistant( .nodes_for_capture_index(key_capture_ix) .next()? .byte_range(); - return Some((key_range, "agent".to_string())); + Some((key_range, "agent".to_string())) } fn rename_edit_prediction_assistant( @@ -37,5 +37,5 @@ fn rename_edit_prediction_assistant( .nodes_for_capture_index(key_capture_ix) .next()? .byte_range(); - return Some((key_range, "enabled_in_text_threads".to_string())); + Some((key_range, "enabled_in_text_threads".to_string())) } diff --git a/crates/migrator/src/migrations/m_2025_06_16/settings.rs b/crates/migrator/src/migrations/m_2025_06_16/settings.rs index cce407e21b..cd79eae204 100644 --- a/crates/migrator/src/migrations/m_2025_06_16/settings.rs +++ b/crates/migrator/src/migrations/m_2025_06_16/settings.rs @@ -40,20 +40,20 @@ fn migrate_context_server_settings( // Parse the server settings to check what keys it contains let mut cursor = server_settings.walk(); for child in server_settings.children(&mut cursor) { - if child.kind() == "pair" { - if let Some(key_node) = child.child_by_field_name("key") { - if let (None, Some(quote_content)) = (column, key_node.child(0)) { - column = Some(quote_content.start_position().column); - } - if let Some(string_content) = key_node.child(1) { - let key = &contents[string_content.byte_range()]; - match key { - // If it already has a source key, don't modify it - "source" => return None, - "command" => has_command = true, - "settings" => has_settings = true, - _ => other_keys += 1, - } + if child.kind() == "pair" + && let Some(key_node) = child.child_by_field_name("key") + { + if let (None, Some(quote_content)) = (column, key_node.child(0)) { + column = Some(quote_content.start_position().column); + } + if let Some(string_content) = key_node.child(1) { + let key = &contents[string_content.byte_range()]; + match key { + // If it already has a source key, don't modify it + "source" => return None, + "command" => has_command = true, + "settings" => has_settings = true, + _ => other_keys += 1, } } } diff --git a/crates/migrator/src/migrations/m_2025_06_25/settings.rs b/crates/migrator/src/migrations/m_2025_06_25/settings.rs index 5dd6c3093a..2bf7658eeb 100644 --- a/crates/migrator/src/migrations/m_2025_06_25/settings.rs +++ b/crates/migrator/src/migrations/m_2025_06_25/settings.rs @@ -84,10 +84,10 @@ fn remove_pair_with_whitespace( } } else { // If no next sibling, check if there's a comma before - if let Some(prev_sibling) = pair_node.prev_sibling() { - if prev_sibling.kind() == "," { - range_to_remove.start = prev_sibling.start_byte(); - } + if let Some(prev_sibling) = pair_node.prev_sibling() + && prev_sibling.kind() == "," + { + range_to_remove.start = prev_sibling.start_byte(); } } @@ -123,10 +123,10 @@ fn remove_pair_with_whitespace( // Also check if we need to include trailing whitespace up to the next line let text_after = &contents[range_to_remove.end..]; - if let Some(newline_pos) = text_after.find('\n') { - if text_after[..newline_pos].chars().all(|c| c.is_whitespace()) { - range_to_remove.end += newline_pos + 1; - } + if let Some(newline_pos) = text_after.find('\n') + && text_after[..newline_pos].chars().all(|c| c.is_whitespace()) + { + range_to_remove.end += newline_pos + 1; } Some((range_to_remove, String::new())) diff --git a/crates/migrator/src/migrations/m_2025_06_27/settings.rs b/crates/migrator/src/migrations/m_2025_06_27/settings.rs index 6156308fce..e3e951b1a6 100644 --- a/crates/migrator/src/migrations/m_2025_06_27/settings.rs +++ b/crates/migrator/src/migrations/m_2025_06_27/settings.rs @@ -56,19 +56,18 @@ fn flatten_context_server_command( let mut cursor = command_object.walk(); for child in command_object.children(&mut cursor) { - if child.kind() == "pair" { - if let Some(key_node) = child.child_by_field_name("key") { - if let Some(string_content) = key_node.child(1) { - let key = &contents[string_content.byte_range()]; - if let Some(value_node) = child.child_by_field_name("value") { - let value_range = value_node.byte_range(); - match key { - "path" => path_value = Some(&contents[value_range]), - "args" => args_value = Some(&contents[value_range]), - "env" => env_value = Some(&contents[value_range]), - _ => {} - } - } + if child.kind() == "pair" + && let Some(key_node) = child.child_by_field_name("key") + && let Some(string_content) = key_node.child(1) + { + let key = &contents[string_content.byte_range()]; + if let Some(value_node) = child.child_by_field_name("value") { + let value_range = value_node.byte_range(); + match key { + "path" => path_value = Some(&contents[value_range]), + "args" => args_value = Some(&contents[value_range]), + "env" => env_value = Some(&contents[value_range]), + _ => {} } } } diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index b425f7f1d5..88e3e12f02 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -37,7 +37,7 @@ fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Result<Opt let mut edits = vec![]; while let Some(mat) = matches.next() { if let Some((_, callback)) = patterns.get(mat.pattern_index) { - edits.extend(callback(&text, &mat, query)); + edits.extend(callback(text, mat, query)); } } @@ -170,7 +170,7 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> { pub fn migrate_edit_prediction_provider_settings(text: &str) -> Result<Option<String>> { migrate( - &text, + text, &[( SETTINGS_NESTED_KEY_VALUE_PATTERN, migrations::m_2025_01_29::replace_edit_prediction_provider_setting, @@ -293,12 +293,12 @@ mod tests { use super::*; fn assert_migrate_keymap(input: &str, output: Option<&str>) { - let migrated = migrate_keymap(&input).unwrap(); + let migrated = migrate_keymap(input).unwrap(); pretty_assertions::assert_eq!(migrated.as_deref(), output); } fn assert_migrate_settings(input: &str, output: Option<&str>) { - let migrated = migrate_settings(&input).unwrap(); + let migrated = migrate_settings(input).unwrap(); pretty_assertions::assert_eq!(migrated.as_deref(), output); } diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index 1305328d38..6bed0a4028 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -76,27 +76,26 @@ impl Anchor { if text_cmp.is_ne() { return text_cmp; } - if self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some() { - if let Some(base_text) = snapshot + if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some()) + && let Some(base_text) = snapshot .diffs .get(&excerpt.buffer_id) .map(|diff| diff.base_text()) - { - let self_anchor = self.diff_base_anchor.filter(|a| base_text.can_resolve(a)); - let other_anchor = other.diff_base_anchor.filter(|a| base_text.can_resolve(a)); - return match (self_anchor, other_anchor) { - (Some(a), Some(b)) => a.cmp(&b, base_text), - (Some(_), None) => match other.text_anchor.bias { - Bias::Left => Ordering::Greater, - Bias::Right => Ordering::Less, - }, - (None, Some(_)) => match self.text_anchor.bias { - Bias::Left => Ordering::Less, - Bias::Right => Ordering::Greater, - }, - (None, None) => Ordering::Equal, - }; - } + { + let self_anchor = self.diff_base_anchor.filter(|a| base_text.can_resolve(a)); + let other_anchor = other.diff_base_anchor.filter(|a| base_text.can_resolve(a)); + return match (self_anchor, other_anchor) { + (Some(a), Some(b)) => a.cmp(&b, base_text), + (Some(_), None) => match other.text_anchor.bias { + Bias::Left => Ordering::Greater, + Bias::Right => Ordering::Less, + }, + (None, Some(_)) => match self.text_anchor.bias { + Bias::Left => Ordering::Less, + Bias::Right => Ordering::Greater, + }, + (None, None) => Ordering::Equal, + }; } } Ordering::Equal @@ -107,51 +106,49 @@ impl Anchor { } pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { - if self.text_anchor.bias != Bias::Left { - if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { - return Self { - buffer_id: self.buffer_id, - excerpt_id: self.excerpt_id, - text_anchor: self.text_anchor.bias_left(&excerpt.buffer), - diff_base_anchor: self.diff_base_anchor.map(|a| { - if let Some(base_text) = snapshot - .diffs - .get(&excerpt.buffer_id) - .map(|diff| diff.base_text()) - { - if a.buffer_id == Some(base_text.remote_id()) { - return a.bias_left(base_text); - } - } - a - }), - }; - } + if self.text_anchor.bias != Bias::Left + && let Some(excerpt) = snapshot.excerpt(self.excerpt_id) + { + return Self { + buffer_id: self.buffer_id, + excerpt_id: self.excerpt_id, + text_anchor: self.text_anchor.bias_left(&excerpt.buffer), + diff_base_anchor: self.diff_base_anchor.map(|a| { + if let Some(base_text) = snapshot + .diffs + .get(&excerpt.buffer_id) + .map(|diff| diff.base_text()) + && a.buffer_id == Some(base_text.remote_id()) + { + return a.bias_left(base_text); + } + a + }), + }; } *self } pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { - if self.text_anchor.bias != Bias::Right { - if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { - return Self { - buffer_id: self.buffer_id, - excerpt_id: self.excerpt_id, - text_anchor: self.text_anchor.bias_right(&excerpt.buffer), - diff_base_anchor: self.diff_base_anchor.map(|a| { - if let Some(base_text) = snapshot - .diffs - .get(&excerpt.buffer_id) - .map(|diff| diff.base_text()) - { - if a.buffer_id == Some(base_text.remote_id()) { - return a.bias_right(&base_text); - } - } - a - }), - }; - } + if self.text_anchor.bias != Bias::Right + && let Some(excerpt) = snapshot.excerpt(self.excerpt_id) + { + return Self { + buffer_id: self.buffer_id, + excerpt_id: self.excerpt_id, + text_anchor: self.text_anchor.bias_right(&excerpt.buffer), + diff_base_anchor: self.diff_base_anchor.map(|a| { + if let Some(base_text) = snapshot + .diffs + .get(&excerpt.buffer_id) + .map(|diff| diff.base_text()) + && a.buffer_id == Some(base_text.remote_id()) + { + return a.bias_right(base_text); + } + a + }), + }; } *self } @@ -212,7 +209,7 @@ impl AnchorRangeExt for Range<Anchor> { } fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool { - self.start.cmp(&other.start, &buffer).is_le() && other.end.cmp(&self.end, &buffer).is_le() + self.start.cmp(&other.start, buffer).is_le() && other.end.cmp(&self.end, buffer).is_le() } fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool { diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index eb12e6929c..162e3bea78 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -1082,11 +1082,11 @@ impl MultiBuffer { let mut ranges: Vec<Range<usize>> = Vec::new(); for edit in edits { - if let Some(last_range) = ranges.last_mut() { - if edit.range.start <= last_range.end { - last_range.end = last_range.end.max(edit.range.end); - continue; - } + if let Some(last_range) = ranges.last_mut() + && edit.range.start <= last_range.end + { + last_range.end = last_range.end.max(edit.range.end); + continue; } ranges.push(edit.range); } @@ -1146,13 +1146,13 @@ impl MultiBuffer { pub fn last_transaction_id(&self, cx: &App) -> Option<TransactionId> { if let Some(buffer) = self.as_singleton() { - return buffer + buffer .read(cx) .peek_undo_stack() - .map(|history_entry| history_entry.transaction_id()); + .map(|history_entry| history_entry.transaction_id()) } else { let last_transaction = self.history.undo_stack.last()?; - return Some(last_transaction.id); + Some(last_transaction.id) } } @@ -1212,25 +1212,24 @@ impl MultiBuffer { for range in buffer.edited_ranges_for_transaction_id::<D>(*buffer_transaction) { for excerpt_id in &buffer_state.excerpts { cursor.seek(excerpt_id, Bias::Left); - if let Some(excerpt) = cursor.item() { - if excerpt.locator == *excerpt_id { - let excerpt_buffer_start = - excerpt.range.context.start.summary::<D>(buffer); - let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(buffer); - let excerpt_range = excerpt_buffer_start..excerpt_buffer_end; - if excerpt_range.contains(&range.start) - && excerpt_range.contains(&range.end) - { - let excerpt_start = D::from_text_summary(&cursor.start().text); + if let Some(excerpt) = cursor.item() + && excerpt.locator == *excerpt_id + { + let excerpt_buffer_start = excerpt.range.context.start.summary::<D>(buffer); + let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(buffer); + let excerpt_range = excerpt_buffer_start..excerpt_buffer_end; + if excerpt_range.contains(&range.start) + && excerpt_range.contains(&range.end) + { + let excerpt_start = D::from_text_summary(&cursor.start().text); - let mut start = excerpt_start; - start.add_assign(&(range.start - excerpt_buffer_start)); - let mut end = excerpt_start; - end.add_assign(&(range.end - excerpt_buffer_start)); + let mut start = excerpt_start; + start.add_assign(&(range.start - excerpt_buffer_start)); + let mut end = excerpt_start; + end.add_assign(&(range.end - excerpt_buffer_start)); - ranges.push(start..end); - break; - } + ranges.push(start..end); + break; } } } @@ -1251,25 +1250,25 @@ impl MultiBuffer { buffer.update(cx, |buffer, _| { buffer.merge_transactions(transaction, destination) }); - } else if let Some(transaction) = self.history.forget(transaction) { - if let Some(destination) = self.history.transaction_mut(destination) { - for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions { - if let Some(destination_buffer_transaction_id) = - destination.buffer_transactions.get(&buffer_id) - { - if let Some(state) = self.buffers.borrow().get(&buffer_id) { - state.buffer.update(cx, |buffer, _| { - buffer.merge_transactions( - buffer_transaction_id, - *destination_buffer_transaction_id, - ) - }); - } - } else { - destination - .buffer_transactions - .insert(buffer_id, buffer_transaction_id); + } else if let Some(transaction) = self.history.forget(transaction) + && let Some(destination) = self.history.transaction_mut(destination) + { + for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions { + if let Some(destination_buffer_transaction_id) = + destination.buffer_transactions.get(&buffer_id) + { + if let Some(state) = self.buffers.borrow().get(&buffer_id) { + state.buffer.update(cx, |buffer, _| { + buffer.merge_transactions( + buffer_transaction_id, + *destination_buffer_transaction_id, + ) + }); } + } else { + destination + .buffer_transactions + .insert(buffer_id, buffer_transaction_id); } } } @@ -1562,11 +1561,11 @@ impl MultiBuffer { }); let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new(); for range in expanded_ranges { - if let Some(last_range) = merged_ranges.last_mut() { - if last_range.context.end >= range.context.start { - last_range.context.end = range.context.end; - continue; - } + if let Some(last_range) = merged_ranges.last_mut() + && last_range.context.end >= range.context.start + { + last_range.context.end = range.context.end; + continue; } merged_ranges.push(range) } @@ -1686,7 +1685,7 @@ impl MultiBuffer { cx: &mut Context<Self>, ) -> (Vec<Range<Anchor>>, bool) { let (excerpt_ids, added_a_new_excerpt) = - self.update_path_excerpts(path, buffer, &buffer_snapshot, new, cx); + self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx); let mut result = Vec::new(); let mut ranges = ranges.into_iter(); @@ -1726,7 +1725,7 @@ impl MultiBuffer { merged_ranges.push(range.clone()); counts.push(1); } - return (merged_ranges, counts); + (merged_ranges, counts) } fn update_path_excerpts( @@ -1784,7 +1783,7 @@ impl MultiBuffer { } Some(( *existing_id, - excerpt.range.context.to_point(&buffer_snapshot), + excerpt.range.context.to_point(buffer_snapshot), )) } else { None @@ -1794,25 +1793,25 @@ impl MultiBuffer { }; if let Some((last_id, last)) = to_insert.last_mut() { - if let Some(new) = new { - if last.context.end >= new.context.start { - last.context.end = last.context.end.max(new.context.end); - excerpt_ids.push(*last_id); - new_iter.next(); - continue; - } + if let Some(new) = new + && last.context.end >= new.context.start + { + last.context.end = last.context.end.max(new.context.end); + excerpt_ids.push(*last_id); + new_iter.next(); + continue; } - if let Some((existing_id, existing_range)) = &existing { - if last.context.end >= existing_range.start { - last.context.end = last.context.end.max(existing_range.end); - to_remove.push(*existing_id); - self.snapshot - .borrow_mut() - .replaced_excerpts - .insert(*existing_id, *last_id); - existing_iter.next(); - continue; - } + if let Some((existing_id, existing_range)) = &existing + && last.context.end >= existing_range.start + { + last.context.end = last.context.end.max(existing_range.end); + to_remove.push(*existing_id); + self.snapshot + .borrow_mut() + .replaced_excerpts + .insert(*existing_id, *last_id); + existing_iter.next(); + continue; } } @@ -2105,10 +2104,10 @@ impl MultiBuffer { .flatten() { cursor.seek_forward(&Some(locator), Bias::Left); - if let Some(excerpt) = cursor.item() { - if excerpt.locator == *locator { - excerpts.push((excerpt.id, excerpt.range.clone())); - } + if let Some(excerpt) = cursor.item() + && excerpt.locator == *locator + { + excerpts.push((excerpt.id, excerpt.range.clone())); } } @@ -2132,22 +2131,21 @@ impl MultiBuffer { let mut result = Vec::new(); for locator in locators { excerpts.seek_forward(&Some(locator), Bias::Left); - if let Some(excerpt) = excerpts.item() { - if excerpt.locator == *locator { - let excerpt_start = excerpts.start().1.clone(); - let excerpt_end = - ExcerptDimension(excerpt_start.0 + excerpt.text_summary.lines); + if let Some(excerpt) = excerpts.item() + && excerpt.locator == *locator + { + let excerpt_start = excerpts.start().1.clone(); + let excerpt_end = ExcerptDimension(excerpt_start.0 + excerpt.text_summary.lines); - diff_transforms.seek_forward(&excerpt_start, Bias::Left); - let overshoot = excerpt_start.0 - diff_transforms.start().0.0; - let start = diff_transforms.start().1.0 + overshoot; + diff_transforms.seek_forward(&excerpt_start, Bias::Left); + let overshoot = excerpt_start.0 - diff_transforms.start().0.0; + let start = diff_transforms.start().1.0 + overshoot; - diff_transforms.seek_forward(&excerpt_end, Bias::Right); - let overshoot = excerpt_end.0 - diff_transforms.start().0.0; - let end = diff_transforms.start().1.0 + overshoot; + diff_transforms.seek_forward(&excerpt_end, Bias::Right); + let overshoot = excerpt_end.0 - diff_transforms.start().0.0; + let end = diff_transforms.start().1.0 + overshoot; - result.push(start..end) - } + result.push(start..end) } } result @@ -2316,12 +2314,12 @@ impl MultiBuffer { // Skip over any subsequent excerpts that are also removed. if let Some(&next_excerpt_id) = excerpt_ids.peek() { let next_locator = snapshot.excerpt_locator_for_id(next_excerpt_id); - if let Some(next_excerpt) = cursor.item() { - if next_excerpt.locator == *next_locator { - excerpt_ids.next(); - excerpt = next_excerpt; - continue 'remove_excerpts; - } + if let Some(next_excerpt) = cursor.item() + && next_excerpt.locator == *next_locator + { + excerpt_ids.next(); + excerpt = next_excerpt; + continue 'remove_excerpts; } } @@ -2484,7 +2482,7 @@ impl MultiBuffer { let base_text_changed = snapshot .diffs .get(&buffer_id) - .map_or(true, |old_diff| !new_diff.base_texts_eq(old_diff)); + .is_none_or(|old_diff| !new_diff.base_texts_eq(old_diff)); snapshot.diffs.insert(buffer_id, new_diff); @@ -2494,33 +2492,33 @@ impl MultiBuffer { .excerpts .cursor::<Dimensions<Option<&Locator>, ExcerptOffset>>(&()); cursor.seek_forward(&Some(locator), Bias::Left); - if let Some(excerpt) = cursor.item() { - if excerpt.locator == *locator { - let excerpt_buffer_range = excerpt.range.context.to_offset(&excerpt.buffer); - if diff_change_range.end < excerpt_buffer_range.start - || diff_change_range.start > excerpt_buffer_range.end - { - continue; - } - let excerpt_start = cursor.start().1; - let excerpt_len = ExcerptOffset::new(excerpt.text_summary.len); - let diff_change_start_in_excerpt = ExcerptOffset::new( - diff_change_range - .start - .saturating_sub(excerpt_buffer_range.start), - ); - let diff_change_end_in_excerpt = ExcerptOffset::new( - diff_change_range - .end - .saturating_sub(excerpt_buffer_range.start), - ); - let edit_start = excerpt_start + diff_change_start_in_excerpt.min(excerpt_len); - let edit_end = excerpt_start + diff_change_end_in_excerpt.min(excerpt_len); - excerpt_edits.push(Edit { - old: edit_start..edit_end, - new: edit_start..edit_end, - }); + if let Some(excerpt) = cursor.item() + && excerpt.locator == *locator + { + let excerpt_buffer_range = excerpt.range.context.to_offset(&excerpt.buffer); + if diff_change_range.end < excerpt_buffer_range.start + || diff_change_range.start > excerpt_buffer_range.end + { + continue; } + let excerpt_start = cursor.start().1; + let excerpt_len = ExcerptOffset::new(excerpt.text_summary.len); + let diff_change_start_in_excerpt = ExcerptOffset::new( + diff_change_range + .start + .saturating_sub(excerpt_buffer_range.start), + ); + let diff_change_end_in_excerpt = ExcerptOffset::new( + diff_change_range + .end + .saturating_sub(excerpt_buffer_range.start), + ); + let edit_start = excerpt_start + diff_change_start_in_excerpt.min(excerpt_len); + let edit_end = excerpt_start + diff_change_end_in_excerpt.min(excerpt_len); + excerpt_edits.push(Edit { + old: edit_start..edit_end, + new: edit_start..edit_end, + }); } } @@ -2778,7 +2776,7 @@ impl MultiBuffer { if diff_hunk.excerpt_id.cmp(&end_excerpt_id, &snapshot).is_gt() { continue; } - if last_hunk_row.map_or(false, |row| row >= diff_hunk.row_range.start) { + if last_hunk_row.is_some_and(|row| row >= diff_hunk.row_range.start) { continue; } let start = Anchor::in_buffer( @@ -3042,7 +3040,7 @@ impl MultiBuffer { is_dirty |= buffer.is_dirty(); has_deleted_file |= buffer .file() - .map_or(false, |file| file.disk_state() == DiskState::Deleted); + .is_some_and(|file| file.disk_state() == DiskState::Deleted); has_conflict |= buffer.has_conflict(); } if edited { @@ -3056,7 +3054,7 @@ impl MultiBuffer { snapshot.has_conflict = has_conflict; for (id, diff) in self.diffs.iter() { - if snapshot.diffs.get(&id).is_none() { + if snapshot.diffs.get(id).is_none() { snapshot.diffs.insert(*id, diff.diff.read(cx).snapshot(cx)); } } @@ -3155,13 +3153,12 @@ impl MultiBuffer { at_transform_boundary = false; let transforms_before_edit = old_diff_transforms.slice(&edit.old.start, Bias::Left); self.append_diff_transforms(&mut new_diff_transforms, transforms_before_edit); - if let Some(transform) = old_diff_transforms.item() { - if old_diff_transforms.end().0 == edit.old.start - && old_diff_transforms.start().0 < edit.old.start - { - self.push_diff_transform(&mut new_diff_transforms, transform.clone()); - old_diff_transforms.next(); - } + if let Some(transform) = old_diff_transforms.item() + && old_diff_transforms.end().0 == edit.old.start + && old_diff_transforms.start().0 < edit.old.start + { + self.push_diff_transform(&mut new_diff_transforms, transform.clone()); + old_diff_transforms.next(); } } @@ -3177,7 +3174,7 @@ impl MultiBuffer { &mut new_diff_transforms, &mut end_of_current_insert, &mut old_expanded_hunks, - &snapshot, + snapshot, change_kind, ); @@ -3201,9 +3198,10 @@ impl MultiBuffer { // If this is the last edit that intersects the current diff transform, // then recreate the content up to the end of this transform, to prepare // for reusing additional slices of the old transforms. - if excerpt_edits.peek().map_or(true, |next_edit| { - next_edit.old.start >= old_diff_transforms.end().0 - }) { + if excerpt_edits + .peek() + .is_none_or(|next_edit| next_edit.old.start >= old_diff_transforms.end().0) + { let keep_next_old_transform = (old_diff_transforms.start().0 >= edit.old.end) && match old_diff_transforms.item() { Some(DiffTransform::BufferContent { @@ -3223,7 +3221,7 @@ impl MultiBuffer { old_expanded_hunks.clear(); self.push_buffer_content_transform( - &snapshot, + snapshot, &mut new_diff_transforms, excerpt_offset, end_of_current_insert, @@ -3431,18 +3429,17 @@ impl MultiBuffer { inserted_hunk_info, summary, }) = subtree.first() - { - if self.extend_last_buffer_content_transform( + && self.extend_last_buffer_content_transform( new_transforms, *inserted_hunk_info, *summary, - ) { - let mut cursor = subtree.cursor::<()>(&()); - cursor.next(); - cursor.next(); - new_transforms.append(cursor.suffix(), &()); - return; - } + ) + { + let mut cursor = subtree.cursor::<()>(&()); + cursor.next(); + cursor.next(); + new_transforms.append(cursor.suffix(), &()); + return; } new_transforms.append(subtree, &()); } @@ -3456,14 +3453,13 @@ impl MultiBuffer { inserted_hunk_info: inserted_hunk_anchor, summary, } = transform - { - if self.extend_last_buffer_content_transform( + && self.extend_last_buffer_content_transform( new_transforms, inserted_hunk_anchor, summary, - ) { - return; - } + ) + { + return; } new_transforms.push(transform, &()); } @@ -3518,11 +3514,10 @@ impl MultiBuffer { summary, inserted_hunk_info: inserted_hunk_anchor, } = last_transform + && *inserted_hunk_anchor == new_inserted_hunk_info { - if *inserted_hunk_anchor == new_inserted_hunk_info { - *summary += summary_to_add; - did_extend = true; - } + *summary += summary_to_add; + did_extend = true; } }, &(), @@ -3601,7 +3596,7 @@ impl MultiBuffer { let mut edits: Vec<(Range<usize>, Arc<str>)> = Vec::new(); let mut last_end = None; for _ in 0..edit_count { - if last_end.map_or(false, |last_end| last_end >= snapshot.len()) { + if last_end.is_some_and(|last_end| last_end >= snapshot.len()) { break; } @@ -3916,8 +3911,8 @@ impl MultiBufferSnapshot { &self, range: Range<T>, ) -> Vec<(&BufferSnapshot, Range<usize>, ExcerptId)> { - let start = range.start.to_offset(&self); - let end = range.end.to_offset(&self); + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); let mut cursor = self.cursor::<usize>(); cursor.seek(&start); @@ -3955,8 +3950,8 @@ impl MultiBufferSnapshot { &self, range: Range<T>, ) -> impl Iterator<Item = (&BufferSnapshot, Range<usize>, ExcerptId, Option<Anchor>)> + '_ { - let start = range.start.to_offset(&self); - let end = range.end.to_offset(&self); + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); let mut cursor = self.cursor::<usize>(); cursor.seek(&start); @@ -4037,10 +4032,10 @@ impl MultiBufferSnapshot { cursor.seek(&query_range.start); - if let Some(region) = cursor.region().filter(|region| !region.is_main_buffer) { - if region.range.start > D::zero(&()) { - cursor.prev() - } + if let Some(region) = cursor.region().filter(|region| !region.is_main_buffer) + && region.range.start > D::zero(&()) + { + cursor.prev() } iter::from_fn(move || { @@ -4070,10 +4065,10 @@ impl MultiBufferSnapshot { buffer_start = cursor.main_buffer_position()?; }; let mut buffer_end = excerpt.range.context.end.summary::<D>(&excerpt.buffer); - if let Some((end_excerpt_id, end_buffer_offset)) = range_end { - if excerpt.id == end_excerpt_id { - buffer_end = buffer_end.min(end_buffer_offset); - } + if let Some((end_excerpt_id, end_buffer_offset)) = range_end + && excerpt.id == end_excerpt_id + { + buffer_end = buffer_end.min(end_buffer_offset); } if let Some(iterator) = @@ -4144,10 +4139,10 @@ impl MultiBufferSnapshot { // When there are no more metadata items for this excerpt, move to the next excerpt. else { current_excerpt_metadata.take(); - if let Some((end_excerpt_id, _)) = range_end { - if excerpt.id == end_excerpt_id { - return None; - } + if let Some((end_excerpt_id, _)) = range_end + && excerpt.id == end_excerpt_id + { + return None; } cursor.next_excerpt(); } @@ -4186,7 +4181,7 @@ impl MultiBufferSnapshot { } let start = Anchor::in_buffer(excerpt.id, excerpt.buffer_id, hunk.buffer_range.start) - .to_point(&self); + .to_point(self); return Some(MultiBufferRow(start.row)); } } @@ -4204,7 +4199,7 @@ impl MultiBufferSnapshot { continue; }; let start = Anchor::in_buffer(excerpt.id, excerpt.buffer_id, hunk.buffer_range.start) - .to_point(&self); + .to_point(self); return Some(MultiBufferRow(start.row)); } } @@ -4455,7 +4450,7 @@ impl MultiBufferSnapshot { let mut buffer_position = region.buffer_range.start; buffer_position.add_assign(&overshoot); let clipped_buffer_position = - clip_buffer_position(®ion.buffer, buffer_position, bias); + clip_buffer_position(region.buffer, buffer_position, bias); let mut position = region.range.start; position.add_assign(&(clipped_buffer_position - region.buffer_range.start)); position @@ -4485,7 +4480,7 @@ impl MultiBufferSnapshot { let buffer_start_value = region.buffer_range.start.value.unwrap(); let mut buffer_key = buffer_start_key; buffer_key.add_assign(&(key - start_key)); - let buffer_value = convert_buffer_dimension(®ion.buffer, buffer_key); + let buffer_value = convert_buffer_dimension(region.buffer, buffer_key); let mut result = start_value; result.add_assign(&(buffer_value - buffer_start_value)); result @@ -4622,20 +4617,20 @@ impl MultiBufferSnapshot { pub fn indent_and_comment_for_line(&self, row: MultiBufferRow, cx: &App) -> String { let mut indent = self.indent_size_for_line(row).chars().collect::<String>(); - if self.language_settings(cx).extend_comment_on_newline { - if let Some(language_scope) = self.language_scope_at(Point::new(row.0, 0)) { - let delimiters = language_scope.line_comment_prefixes(); - for delimiter in delimiters { - if *self - .chars_at(Point::new(row.0, indent.len() as u32)) - .take(delimiter.chars().count()) - .collect::<String>() - .as_str() - == **delimiter - { - indent.push_str(&delimiter); - break; - } + if self.language_settings(cx).extend_comment_on_newline + && let Some(language_scope) = self.language_scope_at(Point::new(row.0, 0)) + { + let delimiters = language_scope.line_comment_prefixes(); + for delimiter in delimiters { + if *self + .chars_at(Point::new(row.0, indent.len() as u32)) + .take(delimiter.chars().count()) + .collect::<String>() + .as_str() + == **delimiter + { + indent.push_str(delimiter); + break; } } } @@ -4655,7 +4650,7 @@ impl MultiBufferSnapshot { return true; } } - return true; + true } pub fn prev_non_blank_row(&self, mut row: MultiBufferRow) -> Option<MultiBufferRow> { @@ -4893,25 +4888,22 @@ impl MultiBufferSnapshot { base_text_byte_range, .. }) => { - if let Some(diff_base_anchor) = &anchor.diff_base_anchor { - if let Some(base_text) = + if let Some(diff_base_anchor) = &anchor.diff_base_anchor + && let Some(base_text) = self.diffs.get(buffer_id).map(|diff| diff.base_text()) + && base_text.can_resolve(diff_base_anchor) + { + let base_text_offset = diff_base_anchor.to_offset(base_text); + if base_text_offset >= base_text_byte_range.start + && base_text_offset <= base_text_byte_range.end { - if base_text.can_resolve(&diff_base_anchor) { - let base_text_offset = diff_base_anchor.to_offset(&base_text); - if base_text_offset >= base_text_byte_range.start - && base_text_offset <= base_text_byte_range.end - { - let position_in_hunk = base_text - .text_summary_for_range::<D, _>( - base_text_byte_range.start..base_text_offset, - ); - position.add_assign(&position_in_hunk); - } else if at_transform_end { - diff_transforms.next(); - continue; - } - } + let position_in_hunk = base_text.text_summary_for_range::<D, _>( + base_text_byte_range.start..base_text_offset, + ); + position.add_assign(&position_in_hunk); + } else if at_transform_end { + diff_transforms.next(); + continue; } } } @@ -4941,20 +4933,19 @@ impl MultiBufferSnapshot { } let mut position = cursor.start().1; - if let Some(excerpt) = cursor.item() { - if excerpt.id == anchor.excerpt_id { - let excerpt_buffer_start = excerpt - .buffer - .offset_for_anchor(&excerpt.range.context.start); - let excerpt_buffer_end = - excerpt.buffer.offset_for_anchor(&excerpt.range.context.end); - let buffer_position = cmp::min( - excerpt_buffer_end, - excerpt.buffer.offset_for_anchor(&anchor.text_anchor), - ); - if buffer_position > excerpt_buffer_start { - position.value += buffer_position - excerpt_buffer_start; - } + if let Some(excerpt) = cursor.item() + && excerpt.id == anchor.excerpt_id + { + let excerpt_buffer_start = excerpt + .buffer + .offset_for_anchor(&excerpt.range.context.start); + let excerpt_buffer_end = excerpt.buffer.offset_for_anchor(&excerpt.range.context.end); + let buffer_position = cmp::min( + excerpt_buffer_end, + excerpt.buffer.offset_for_anchor(&anchor.text_anchor), + ); + if buffer_position > excerpt_buffer_start { + position.value += buffer_position - excerpt_buffer_start; } } position @@ -4964,7 +4955,7 @@ impl MultiBufferSnapshot { while let Some(replacement) = self.replaced_excerpts.get(&excerpt_id) { excerpt_id = *replacement; } - return excerpt_id; + excerpt_id } pub fn summaries_for_anchors<'a, D, I>(&'a self, anchors: I) -> Vec<D> @@ -5082,9 +5073,9 @@ impl MultiBufferSnapshot { if point == region.range.end.key && region.has_trailing_newline { position.add_assign(&D::from_text_summary(&TextSummary::newline())); } - return Some(position); + Some(position) } else { - return Some(D::from_text_summary(&self.text_summary())); + Some(D::from_text_summary(&self.text_summary())) } }) } @@ -5124,7 +5115,7 @@ impl MultiBufferSnapshot { // Leave min and max anchors unchanged if invalid or // if the old excerpt still exists at this location let mut kept_position = next_excerpt - .map_or(false, |e| e.id == old_excerpt_id && e.contains(&anchor)) + .is_some_and(|e| e.id == old_excerpt_id && e.contains(&anchor)) || old_excerpt_id == ExcerptId::max() || old_excerpt_id == ExcerptId::min(); @@ -5211,15 +5202,12 @@ impl MultiBufferSnapshot { .cursor::<Dimensions<usize, ExcerptOffset>>(&()); diff_transforms.seek(&offset, Bias::Right); - if offset == diff_transforms.start().0 && bias == Bias::Left { - if let Some(prev_item) = diff_transforms.prev_item() { - match prev_item { - DiffTransform::DeletedHunk { .. } => { - diff_transforms.prev(); - } - _ => {} - } - } + if offset == diff_transforms.start().0 + && bias == Bias::Left + && let Some(prev_item) = diff_transforms.prev_item() + && let DiffTransform::DeletedHunk { .. } = prev_item + { + diff_transforms.prev(); } let offset_in_transform = offset - diff_transforms.start().0; let mut excerpt_offset = diff_transforms.start().1; @@ -5296,17 +5284,17 @@ impl MultiBufferSnapshot { let locator = self.excerpt_locator_for_id(excerpt_id); let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&()); cursor.seek(locator, Bias::Left); - if let Some(excerpt) = cursor.item() { - if excerpt.id == excerpt_id { - let text_anchor = excerpt.clip_anchor(text_anchor); - drop(cursor); - return Some(Anchor { - buffer_id: Some(excerpt.buffer_id), - excerpt_id, - text_anchor, - diff_base_anchor: None, - }); - } + if let Some(excerpt) = cursor.item() + && excerpt.id == excerpt_id + { + let text_anchor = excerpt.clip_anchor(text_anchor); + drop(cursor); + return Some(Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id, + text_anchor, + diff_base_anchor: None, + }); } None } @@ -5491,7 +5479,7 @@ impl MultiBufferSnapshot { let range_filter = |open: Range<usize>, close: Range<usize>| -> bool { excerpt_buffer_range.contains(&open.start) && excerpt_buffer_range.contains(&close.end) - && range_filter.map_or(true, |filter| filter(buffer, open, close)) + && range_filter.is_none_or(|filter| filter(buffer, open, close)) }; let (open, close) = excerpt.buffer().innermost_enclosing_bracket_ranges( @@ -5651,10 +5639,10 @@ impl MultiBufferSnapshot { .buffer .line_indents_in_row_range(buffer_start_row..buffer_end_row); cursor.next(); - return Some(line_indents.map(move |(buffer_row, indent)| { + Some(line_indents.map(move |(buffer_row, indent)| { let row = region.range.start.row + (buffer_row - region.buffer_range.start.row); (MultiBufferRow(row), indent, ®ion.excerpt.buffer) - })); + })) }) .flatten() } @@ -5691,10 +5679,10 @@ impl MultiBufferSnapshot { .buffer .reversed_line_indents_in_row_range(buffer_start_row..buffer_end_row); cursor.prev(); - return Some(line_indents.map(move |(buffer_row, indent)| { + Some(line_indents.map(move |(buffer_row, indent)| { let row = region.range.start.row + (buffer_row - region.buffer_range.start.row); (MultiBufferRow(row), indent, ®ion.excerpt.buffer) - })); + })) }) .flatten() } @@ -5860,10 +5848,10 @@ impl MultiBufferSnapshot { let current_depth = indent_stack.len() as u32; // Avoid retrieving the language settings repeatedly for every buffer row. - if let Some((prev_buffer_id, _)) = &prev_settings { - if prev_buffer_id != &buffer.remote_id() { - prev_settings.take(); - } + if let Some((prev_buffer_id, _)) = &prev_settings + && prev_buffer_id != &buffer.remote_id() + { + prev_settings.take(); } let settings = &prev_settings .get_or_insert_with(|| { @@ -6192,10 +6180,10 @@ impl MultiBufferSnapshot { } else { let mut cursor = self.excerpt_ids.cursor::<ExcerptId>(&()); cursor.seek(&id, Bias::Left); - if let Some(entry) = cursor.item() { - if entry.id == id { - return &entry.locator; - } + if let Some(entry) = cursor.item() + && entry.id == id + { + return &entry.locator; } panic!("invalid excerpt id {id:?}") } @@ -6272,10 +6260,10 @@ impl MultiBufferSnapshot { pub fn buffer_range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<Range<text::Anchor>> { let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&()); let locator = self.excerpt_locator_for_id(excerpt_id); - if cursor.seek(&Some(locator), Bias::Left) { - if let Some(excerpt) = cursor.item() { - return Some(excerpt.range.context.clone()); - } + if cursor.seek(&Some(locator), Bias::Left) + && let Some(excerpt) = cursor.item() + { + return Some(excerpt.range.context.clone()); } None } @@ -6284,10 +6272,10 @@ impl MultiBufferSnapshot { let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&()); let locator = self.excerpt_locator_for_id(excerpt_id); cursor.seek(&Some(locator), Bias::Left); - if let Some(excerpt) = cursor.item() { - if excerpt.id == excerpt_id { - return Some(excerpt); - } + if let Some(excerpt) = cursor.item() + && excerpt.id == excerpt_id + { + return Some(excerpt); } None } @@ -6418,7 +6406,7 @@ impl MultiBufferSnapshot { for (ix, entry) in excerpt_ids.iter().enumerate() { if ix == 0 { - if entry.id.cmp(&ExcerptId::min(), &self).is_le() { + if entry.id.cmp(&ExcerptId::min(), self).is_le() { panic!("invalid first excerpt id {:?}", entry.id); } } else if entry.id <= excerpt_ids[ix - 1].id { @@ -6446,13 +6434,12 @@ impl MultiBufferSnapshot { inserted_hunk_info: prev_inserted_hunk_info, .. }) = prev_transform + && *inserted_hunk_info == *prev_inserted_hunk_info { - if *inserted_hunk_info == *prev_inserted_hunk_info { - panic!( - "multiple adjacent buffer content transforms with is_inserted_hunk = {inserted_hunk_info:?}. transforms: {:+?}", - self.diff_transforms.items(&()) - ); - } + panic!( + "multiple adjacent buffer content transforms with is_inserted_hunk = {inserted_hunk_info:?}. transforms: {:+?}", + self.diff_transforms.items(&()) + ); } if summary.len == 0 && !self.is_empty() { panic!("empty buffer content transform"); @@ -6552,14 +6539,12 @@ where self.excerpts.next(); } else if let Some(DiffTransform::DeletedHunk { hunk_info, .. }) = self.diff_transforms.item() - { - if self + && self .excerpts .item() - .map_or(false, |excerpt| excerpt.id != hunk_info.excerpt_id) - { - self.excerpts.next(); - } + .is_some_and(|excerpt| excerpt.id != hunk_info.excerpt_id) + { + self.excerpts.next(); } } } @@ -6604,7 +6589,7 @@ where let prev_transform = self.diff_transforms.item(); self.diff_transforms.next(); - prev_transform.map_or(true, |next_transform| { + prev_transform.is_none_or(|next_transform| { matches!(next_transform, DiffTransform::BufferContent { .. }) }) } @@ -6619,12 +6604,12 @@ where } let next_transform = self.diff_transforms.next_item(); - next_transform.map_or(true, |next_transform| match next_transform { + next_transform.is_none_or(|next_transform| match next_transform { DiffTransform::BufferContent { .. } => true, DiffTransform::DeletedHunk { hunk_info, .. } => self .excerpts .item() - .map_or(false, |excerpt| excerpt.id != hunk_info.excerpt_id), + .is_some_and(|excerpt| excerpt.id != hunk_info.excerpt_id), }) } @@ -6648,7 +6633,7 @@ where hunk_info, .. } => { - let diff = self.diffs.get(&buffer_id)?; + let diff = self.diffs.get(buffer_id)?; let buffer = diff.base_text(); let mut rope_cursor = buffer.as_rope().cursor(0); let buffer_start = rope_cursor.summary::<D>(base_text_byte_range.start); @@ -6657,7 +6642,7 @@ where buffer_end.add_assign(&buffer_range_len); let start = self.diff_transforms.start().output_dimension.0; let end = self.diff_transforms.end().output_dimension.0; - return Some(MultiBufferRegion { + Some(MultiBufferRegion { buffer, excerpt, has_trailing_newline: *has_trailing_newline, @@ -6667,7 +6652,7 @@ where )), buffer_range: buffer_start..buffer_end, range: start..end, - }); + }) } DiffTransform::BufferContent { inserted_hunk_info, .. @@ -7505,61 +7490,59 @@ impl Iterator for MultiBufferRows<'_> { self.cursor.next(); if let Some(next_region) = self.cursor.region() { region = next_region; - } else { - if self.point == self.cursor.diff_transforms.end().output_dimension.0 { - let multibuffer_row = MultiBufferRow(self.point.row); - let last_excerpt = self - .cursor - .excerpts - .item() - .or(self.cursor.excerpts.prev_item())?; - let last_row = last_excerpt - .range - .context - .end - .to_point(&last_excerpt.buffer) - .row; + } else if self.point == self.cursor.diff_transforms.end().output_dimension.0 { + let multibuffer_row = MultiBufferRow(self.point.row); + let last_excerpt = self + .cursor + .excerpts + .item() + .or(self.cursor.excerpts.prev_item())?; + let last_row = last_excerpt + .range + .context + .end + .to_point(&last_excerpt.buffer) + .row; - let first_row = last_excerpt - .range - .context - .start - .to_point(&last_excerpt.buffer) - .row; + let first_row = last_excerpt + .range + .context + .start + .to_point(&last_excerpt.buffer) + .row; - let expand_info = if self.is_singleton { - None - } else { - let needs_expand_up = first_row == last_row - && last_row > 0 - && !region.diff_hunk_status.is_some_and(|d| d.is_deleted()); - let needs_expand_down = last_row < last_excerpt.buffer.max_point().row; - - if needs_expand_up && needs_expand_down { - Some(ExpandExcerptDirection::UpAndDown) - } else if needs_expand_up { - Some(ExpandExcerptDirection::Up) - } else if needs_expand_down { - Some(ExpandExcerptDirection::Down) - } else { - None - } - .map(|direction| ExpandInfo { - direction, - excerpt_id: last_excerpt.id, - }) - }; - self.point += Point::new(1, 0); - return Some(RowInfo { - buffer_id: Some(last_excerpt.buffer_id), - buffer_row: Some(last_row), - multibuffer_row: Some(multibuffer_row), - diff_status: None, - expand_info, - }); + let expand_info = if self.is_singleton { + None } else { - return None; - } + let needs_expand_up = first_row == last_row + && last_row > 0 + && !region.diff_hunk_status.is_some_and(|d| d.is_deleted()); + let needs_expand_down = last_row < last_excerpt.buffer.max_point().row; + + if needs_expand_up && needs_expand_down { + Some(ExpandExcerptDirection::UpAndDown) + } else if needs_expand_up { + Some(ExpandExcerptDirection::Up) + } else if needs_expand_down { + Some(ExpandExcerptDirection::Down) + } else { + None + } + .map(|direction| ExpandInfo { + direction, + excerpt_id: last_excerpt.id, + }) + }; + self.point += Point::new(1, 0); + return Some(RowInfo { + buffer_id: Some(last_excerpt.buffer_id), + buffer_row: Some(last_row), + multibuffer_row: Some(multibuffer_row), + diff_status: None, + expand_info, + }); + } else { + return None; }; } @@ -7767,7 +7750,7 @@ impl<'a> Iterator for MultiBufferChunks<'a> { } chunks } else { - let base_buffer = &self.diffs.get(&buffer_id)?.base_text(); + let base_buffer = &self.diffs.get(buffer_id)?.base_text(); base_buffer.chunks(base_text_start..base_text_end, self.language_aware) }; @@ -7855,10 +7838,11 @@ impl io::Read for ReversedMultiBufferBytes<'_> { if len > 0 { self.range.end -= len; self.chunk = &self.chunk[..self.chunk.len() - len]; - if !self.range.is_empty() && self.chunk.is_empty() { - if let Some(chunk) = self.chunks.next() { - self.chunk = chunk.as_bytes(); - } + if !self.range.is_empty() + && self.chunk.is_empty() + && let Some(chunk) = self.chunks.next() + { + self.chunk = chunk.as_bytes(); } } Ok(len) diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index 824efa559f..598ee0f9cb 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -473,7 +473,7 @@ fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) { let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n"; let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n"; let buffer = cx.new(|cx| Buffer::local(text, cx)); - let diff = cx.new(|cx| BufferDiff::new_with_base_text(&base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| { @@ -2265,14 +2265,14 @@ impl ReferenceMultibuffer { } if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| { - expanded_anchor.to_offset(&buffer).max(buffer_range.start) + expanded_anchor.to_offset(buffer).max(buffer_range.start) == hunk_range.start.max(buffer_range.start) }) { log::trace!("skipping a hunk that's not marked as expanded"); continue; } - if !hunk.buffer_range.start.is_valid(&buffer) { + if !hunk.buffer_range.start.is_valid(buffer) { log::trace!("skipping hunk with deleted start: {:?}", hunk.range); continue; } @@ -2449,7 +2449,7 @@ impl ReferenceMultibuffer { return false; } while let Some(hunk) = hunks.peek() { - match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) { + match hunk.buffer_range.start.cmp(hunk_anchor, &buffer) { cmp::Ordering::Less => { hunks.next(); } @@ -2519,8 +2519,8 @@ async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) { let mut seen_ranges = Vec::default(); for (_, buf, range) in snapshot.excerpts() { - let start = range.context.start.to_point(&buf); - let end = range.context.end.to_point(&buf); + let start = range.context.start.to_point(buf); + let end = range.context.end.to_point(buf); seen_ranges.push(start..end); if let Some(last_end) = last_end.take() { @@ -2739,9 +2739,8 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { let id = buffer_handle.read(cx).remote_id(); if multibuffer.diff_for(id).is_none() { let base_text = base_texts.get(&id).unwrap(); - let diff = cx.new(|cx| { - BufferDiff::new_with_base_text(base_text, &buffer_handle, cx) - }); + let diff = cx + .new(|cx| BufferDiff::new_with_base_text(base_text, buffer_handle, cx)); reference.add_diff(diff.clone(), cx); multibuffer.add_diff(diff, cx) } @@ -3593,24 +3592,20 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) { for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] { for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() { - if ix > 0 { - if *offset == 252 { - if offset > &offsets[ix - 1] { - let prev_anchor = left_anchors[ix - 1]; - assert!( - anchor.cmp(&prev_anchor, snapshot).is_gt(), - "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()", - offsets[ix], - offsets[ix - 1], - ); - assert!( - prev_anchor.cmp(&anchor, snapshot).is_lt(), - "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()", - offsets[ix - 1], - offsets[ix], - ); - } - } + if ix > 0 && *offset == 252 && offset > &offsets[ix - 1] { + let prev_anchor = left_anchors[ix - 1]; + assert!( + anchor.cmp(&prev_anchor, snapshot).is_gt(), + "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()", + offsets[ix], + offsets[ix - 1], + ); + assert!( + prev_anchor.cmp(anchor, snapshot).is_lt(), + "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()", + offsets[ix - 1], + offsets[ix], + ); } } } diff --git a/crates/multi_buffer/src/position.rs b/crates/multi_buffer/src/position.rs index 0650875059..8a3ce78d0d 100644 --- a/crates/multi_buffer/src/position.rs +++ b/crates/multi_buffer/src/position.rs @@ -126,17 +126,17 @@ impl<T> Default for TypedRow<T> { impl<T> PartialOrd for TypedOffset<T> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - Some(self.cmp(&other)) + Some(self.cmp(other)) } } impl<T> PartialOrd for TypedPoint<T> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - Some(self.cmp(&other)) + Some(self.cmp(other)) } } impl<T> PartialOrd for TypedRow<T> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - Some(self.cmp(&other)) + Some(self.cmp(other)) } } diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index f92c122e71..9d41eb1562 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -76,9 +76,8 @@ impl NodeRuntime { let mut state = self.0.lock().await; let options = loop { - match state.options.borrow().as_ref() { - Some(options) => break options.clone(), - None => {} + if let Some(options) = state.options.borrow().as_ref() { + break options.clone(); } match state.options.changed().await { Ok(()) => {} @@ -197,7 +196,7 @@ impl NodeRuntime { state.instance = Some(instance.boxed_clone()); state.last_options = Some(options); - return instance; + instance } pub async fn binary_path(&self) -> Result<PathBuf> { diff --git a/crates/notifications/src/notification_store.rs b/crates/notifications/src/notification_store.rs index 29653748e4..af2601bd18 100644 --- a/crates/notifications/src/notification_store.rs +++ b/crates/notifications/src/notification_store.rs @@ -138,10 +138,10 @@ impl NotificationStore { pub fn notification_for_id(&self, id: u64) -> Option<&NotificationEntry> { let mut cursor = self.notifications.cursor::<NotificationId>(&()); cursor.seek(&NotificationId(id), Bias::Left); - if let Some(item) = cursor.item() { - if item.id == id { - return Some(item); - } + if let Some(item) = cursor.item() + && item.id == id + { + return Some(item); } None } @@ -229,25 +229,24 @@ impl NotificationStore { mut cx: AsyncApp, ) -> Result<()> { this.update(&mut cx, |this, cx| { - if let Some(notification) = envelope.payload.notification { - if let Some(rpc::Notification::ChannelMessageMention { message_id, .. }) = + if let Some(notification) = envelope.payload.notification + && let Some(rpc::Notification::ChannelMessageMention { message_id, .. }) = Notification::from_proto(¬ification) - { - let fetch_message_task = this.channel_store.update(cx, |this, cx| { - this.fetch_channel_messages(vec![message_id], cx) - }); + { + let fetch_message_task = this.channel_store.update(cx, |this, cx| { + this.fetch_channel_messages(vec![message_id], cx) + }); - cx.spawn(async move |this, cx| { - let messages = fetch_message_task.await?; - this.update(cx, move |this, cx| { - for message in messages { - this.channel_messages.insert(message_id, message); - } - cx.notify(); - }) + cx.spawn(async move |this, cx| { + let messages = fetch_message_task.await?; + this.update(cx, move |this, cx| { + for message in messages { + this.channel_messages.insert(message_id, message); + } + cx.notify(); }) - .detach_and_log_err(cx) - } + }) + .detach_and_log_err(cx) } Ok(()) })? @@ -390,12 +389,12 @@ impl NotificationStore { }); } } - } else if let Some(new_notification) = &new_notification { - if is_new { - cx.emit(NotificationEvent::NewNotification { - entry: new_notification.clone(), - }); - } + } else if let Some(new_notification) = &new_notification + && is_new + { + cx.emit(NotificationEvent::NewNotification { + entry: new_notification.clone(), + }); } if let Some(notification) = new_notification { diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs index bb1932bdf2..672bcf1cd9 100644 --- a/crates/onboarding/src/ai_setup_page.rs +++ b/crates/onboarding/src/ai_setup_page.rs @@ -19,7 +19,7 @@ use util::ResultExt; use workspace::{ModalView, Workspace}; use zed_actions::agent::OpenSettings; -const FEATURED_PROVIDERS: [&'static str; 4] = ["anthropic", "google", "openai", "ollama"]; +const FEATURED_PROVIDERS: [&str; 4] = ["anthropic", "google", "openai", "ollama"]; fn render_llm_provider_section( tab_index: &mut isize, @@ -329,7 +329,11 @@ impl AiConfigurationModal { cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); - let configuration_view = selected_provider.configuration_view(window, cx); + let configuration_view = selected_provider.configuration_view( + language_model::ConfigurationViewTargetAgent::ZedAgent, + window, + cx, + ); Self { focus_handle, @@ -406,7 +410,7 @@ impl AiPrivacyTooltip { impl Render for AiPrivacyTooltip { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { - const DESCRIPTION: &'static str = "We believe in opt-in data sharing as the default for building AI products, rather than opt-out. We'll only use or store your data if you affirmatively send it to us. "; + const DESCRIPTION: &str = "We believe in opt-in data sharing as the default for building AI products, rather than opt-out. We'll only use or store your data if you affirmatively send it to us. "; tooltip_container(window, cx, move |this, _, _| { this.child( diff --git a/crates/onboarding/src/basics_page.rs b/crates/onboarding/src/basics_page.rs index 86ddc22a86..77a70dfc8d 100644 --- a/crates/onboarding/src/basics_page.rs +++ b/crates/onboarding/src/basics_page.rs @@ -16,6 +16,23 @@ use vim_mode_setting::VimModeSetting; use crate::theme_preview::{ThemePreviewStyle, ThemePreviewTile}; +const LIGHT_THEMES: [&str; 3] = ["One Light", "Ayu Light", "Gruvbox Light"]; +const DARK_THEMES: [&str; 3] = ["One Dark", "Ayu Dark", "Gruvbox Dark"]; +const FAMILY_NAMES: [SharedString; 3] = [ + SharedString::new_static("One"), + SharedString::new_static("Ayu"), + SharedString::new_static("Gruvbox"), +]; + +fn get_theme_family_themes(theme_name: &str) -> Option<(&'static str, &'static str)> { + for i in 0..LIGHT_THEMES.len() { + if LIGHT_THEMES[i] == theme_name || DARK_THEMES[i] == theme_name { + return Some((LIGHT_THEMES[i], DARK_THEMES[i])); + } + } + None +} + fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement { let theme_selection = ThemeSettings::get_global(cx).theme_selection.clone(); let system_appearance = theme::SystemAppearance::global(cx); @@ -90,14 +107,6 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement }; let current_theme_name = theme_selection.theme(appearance); - const LIGHT_THEMES: [&'static str; 3] = ["One Light", "Ayu Light", "Gruvbox Light"]; - const DARK_THEMES: [&'static str; 3] = ["One Dark", "Ayu Dark", "Gruvbox Dark"]; - const FAMILY_NAMES: [SharedString; 3] = [ - SharedString::new_static("One"), - SharedString::new_static("Ayu"), - SharedString::new_static("Gruvbox"), - ]; - let theme_names = match appearance { Appearance::Light => LIGHT_THEMES, Appearance::Dark => DARK_THEMES, @@ -105,7 +114,7 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement let themes = theme_names.map(|theme| theme_registry.get(theme).unwrap()); - let theme_previews = [0, 1, 2].map(|index| { + [0, 1, 2].map(|index| { let theme = &themes[index]; let is_selected = theme.name == current_theme_name; let name = theme.name.clone(); @@ -167,9 +176,7 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement .color(Color::Muted) .size(LabelSize::Small), ) - }); - - theme_previews + }) } fn write_mode_change(mode: ThemeMode, cx: &mut App) { @@ -184,10 +191,13 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement let theme = theme.into(); update_settings_file::<ThemeSettings>(fs, cx, move |settings, cx| { if theme_mode == ThemeMode::System { + let (light_theme, dark_theme) = + get_theme_family_themes(&theme).unwrap_or((theme.as_ref(), theme.as_ref())); + settings.theme = Some(ThemeSelection::Dynamic { mode: ThemeMode::System, - light: ThemeName(theme.clone()), - dark: ThemeName(theme.clone()), + light: ThemeName(light_theme.into()), + dark: ThemeName(dark_theme.into()), }); } else { let appearance = *SystemAppearance::global(cx); diff --git a/crates/onboarding/src/editing_page.rs b/crates/onboarding/src/editing_page.rs index d941a0315a..60a9856abe 100644 --- a/crates/onboarding/src/editing_page.rs +++ b/crates/onboarding/src/editing_page.rs @@ -605,7 +605,7 @@ fn render_popular_settings_section( window: &mut Window, cx: &mut App, ) -> impl IntoElement { - const LIGATURE_TOOLTIP: &'static str = + const LIGATURE_TOOLTIP: &str = "Font ligatures combine two characters into one. For example, turning =/= into ≠."; v_flex() diff --git a/crates/onboarding/src/onboarding.rs b/crates/onboarding/src/onboarding.rs index e07a8dc9fb..884374a72f 100644 --- a/crates/onboarding/src/onboarding.rs +++ b/crates/onboarding/src/onboarding.rs @@ -494,7 +494,7 @@ impl Onboarding { window .spawn(cx, async move |cx| { client - .sign_in_with_optional_connect(true, &cx) + .sign_in_with_optional_connect(true, cx) .await .notify_async_err(cx); }) diff --git a/crates/onboarding/src/theme_preview.rs b/crates/onboarding/src/theme_preview.rs index 9f299eb6ea..6a072b00e9 100644 --- a/crates/onboarding/src/theme_preview.rs +++ b/crates/onboarding/src/theme_preview.rs @@ -210,12 +210,12 @@ impl ThemePreviewTile { } fn render_borderless(seed: f32, theme: Arc<Theme>) -> impl IntoElement { - return Self::render_editor( + Self::render_editor( seed, theme, Self::SIDEBAR_WIDTH_DEFAULT, Self::SKELETON_HEIGHT_DEFAULT, - ); + ) } fn render_border(seed: f32, theme: Arc<Theme>) -> impl IntoElement { @@ -246,7 +246,7 @@ impl ThemePreviewTile { ) -> impl IntoElement { let sidebar_width = relative(0.20); - return div() + div() .size_full() .p(Self::ROOT_PADDING) .rounded(Self::ROOT_RADIUS) @@ -278,7 +278,7 @@ impl ThemePreviewTile { )), ), ) - .into_any_element(); + .into_any_element() } } diff --git a/crates/onboarding/src/welcome.rs b/crates/onboarding/src/welcome.rs index 610f6a98e3..3fe9c32a48 100644 --- a/crates/onboarding/src/welcome.rs +++ b/crates/onboarding/src/welcome.rs @@ -104,7 +104,7 @@ impl<const COLS: usize> Section<COLS> { self.entries .iter() .enumerate() - .map(|(index, entry)| entry.render(index_offset + index, &focus, window, cx)), + .map(|(index, entry)| entry.render(index_offset + index, focus, window, cx)), ) } } diff --git a/crates/open_ai/src/open_ai.rs b/crates/open_ai/src/open_ai.rs index 1fb9a1342c..acf6ec434a 100644 --- a/crates/open_ai/src/open_ai.rs +++ b/crates/open_ai/src/open_ai.rs @@ -9,7 +9,7 @@ use strum::EnumIter; pub const OPEN_AI_API_URL: &str = "https://api.openai.com/v1"; fn is_none_or_empty<T: AsRef<[U]>, U>(opt: &Option<T>) -> bool { - opt.as_ref().map_or(true, |v| v.as_ref().is_empty()) + opt.as_ref().is_none_or(|v| v.as_ref().is_empty()) } #[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)] @@ -241,7 +241,7 @@ impl Model { /// /// If the model does not support the parameter, do not pass it up. pub fn supports_prompt_cache_key(&self) -> bool { - return true; + true } } diff --git a/crates/open_router/src/open_router.rs b/crates/open_router/src/open_router.rs index 3e6e406d98..b7e6d69d8f 100644 --- a/crates/open_router/src/open_router.rs +++ b/crates/open_router/src/open_router.rs @@ -8,7 +8,7 @@ use std::convert::TryFrom; pub const OPEN_ROUTER_API_URL: &str = "https://openrouter.ai/api/v1"; fn is_none_or_empty<T: AsRef<[U]>, U>(opt: &Option<T>) -> bool { - opt.as_ref().map_or(true, |v| v.as_ref().is_empty()) + opt.as_ref().is_none_or(|v| v.as_ref().is_empty()) } #[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)] @@ -240,10 +240,10 @@ impl MessageContent { impl From<Vec<MessagePart>> for MessageContent { fn from(parts: Vec<MessagePart>) -> Self { - if parts.len() == 1 { - if let MessagePart::Text { text } = &parts[0] { - return Self::Plain(text.clone()); - } + if parts.len() == 1 + && let MessagePart::Text { text } = &parts[0] + { + return Self::Plain(text.clone()); } Self::Multipart(parts) } diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 5328620ab3..3f072c1425 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -498,16 +498,16 @@ impl SearchData { && multi_buffer_snapshot .chars_at(extended_context_left_border) .last() - .map_or(false, |c| !c.is_whitespace()); + .is_some_and(|c| !c.is_whitespace()); let truncated_right = entire_context_text .chars() .last() - .map_or(true, |c| !c.is_whitespace()) + .is_none_or(|c| !c.is_whitespace()) && extended_context_right_border > context_right_border && multi_buffer_snapshot .chars_at(extended_context_right_border) .next() - .map_or(false, |c| !c.is_whitespace()); + .is_some_and(|c| !c.is_whitespace()); search_match_indices.iter_mut().for_each(|range| { range.start = multi_buffer_snapshot.clip_offset( range.start.saturating_sub(left_whitespaces_offset), @@ -728,7 +728,8 @@ impl OutlinePanel { ) -> Entity<Self> { let project = workspace.project().clone(); let workspace_handle = cx.entity().downgrade(); - let outline_panel = cx.new(|cx| { + + cx.new(|cx| { let filter_editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); editor.set_placeholder_text("Filter...", cx); @@ -896,9 +897,7 @@ impl OutlinePanel { outline_panel.replace_active_editor(item, editor, window, cx); } outline_panel - }); - - outline_panel + }) } fn serialization_key(workspace: &Workspace) -> Option<String> { @@ -1154,12 +1153,11 @@ impl OutlinePanel { }); } else { let mut offset = Point::default(); - if let Some(buffer_id) = scroll_to_buffer { - if multi_buffer_snapshot.as_singleton().is_none() - && !active_editor.read(cx).is_buffer_folded(buffer_id, cx) - { - offset.y = -(active_editor.read(cx).file_header_size() as f32); - } + if let Some(buffer_id) = scroll_to_buffer + && multi_buffer_snapshot.as_singleton().is_none() + && !active_editor.read(cx).is_buffer_folded(buffer_id, cx) + { + offset.y = -(active_editor.read(cx).file_header_size() as f32); } active_editor.update(cx, |editor, cx| { @@ -1244,7 +1242,7 @@ impl OutlinePanel { dirs_worktree_id == worktree_id && dirs .last() - .map_or(false, |dir| dir.path.as_ref() == parent_path) + .is_some_and(|dir| dir.path.as_ref() == parent_path) } _ => false, }) @@ -1438,9 +1436,7 @@ impl OutlinePanel { if self .unfolded_dirs .get(&directory_worktree) - .map_or(true, |unfolded_dirs| { - !unfolded_dirs.contains(&directory_entry.id) - }) + .is_none_or(|unfolded_dirs| !unfolded_dirs.contains(&directory_entry.id)) { return false; } @@ -1590,16 +1586,14 @@ impl OutlinePanel { } PanelEntry::FoldedDirs(folded_dirs) => { let mut folded = false; - if let Some(dir_entry) = folded_dirs.entries.last() { - if self + if let Some(dir_entry) = folded_dirs.entries.last() + && self .collapsed_entries .insert(CollapsedEntry::Dir(folded_dirs.worktree_id, dir_entry.id)) - { - folded = true; - buffers_to_fold.extend( - self.buffers_inside_directory(folded_dirs.worktree_id, dir_entry), - ); - } + { + folded = true; + buffers_to_fold + .extend(self.buffers_inside_directory(folded_dirs.worktree_id, dir_entry)); } folded } @@ -2092,11 +2086,11 @@ impl OutlinePanel { dirs_to_expand.push(current_entry.id); } - if traversal.back_to_parent() { - if let Some(parent_entry) = traversal.entry() { - current_entry = parent_entry.clone(); - continue; - } + if traversal.back_to_parent() + && let Some(parent_entry) = traversal.entry() + { + current_entry = parent_entry.clone(); + continue; } break; } @@ -2143,7 +2137,7 @@ impl OutlinePanel { ExcerptOutlines::Invalidated(outlines) => Some(outlines), ExcerptOutlines::NotFetched => None, }) - .map_or(false, |outlines| !outlines.is_empty()); + .is_some_and(|outlines| !outlines.is_empty()); let is_expanded = !self .collapsed_entries .contains(&CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)); @@ -2459,17 +2453,17 @@ impl OutlinePanel { let search_data = match render_data.get() { Some(search_data) => search_data, None => { - if let ItemsDisplayMode::Search(search_state) = &mut self.mode { - if let Some(multi_buffer_snapshot) = multi_buffer_snapshot { - search_state - .highlight_search_match_tx - .try_send(HighlightArguments { - multi_buffer_snapshot: multi_buffer_snapshot.clone(), - match_range: match_range.clone(), - search_data: Arc::clone(render_data), - }) - .ok(); - } + if let ItemsDisplayMode::Search(search_state) = &mut self.mode + && let Some(multi_buffer_snapshot) = multi_buffer_snapshot + { + search_state + .highlight_search_match_tx + .try_send(HighlightArguments { + multi_buffer_snapshot: multi_buffer_snapshot.clone(), + match_range: match_range.clone(), + search_data: Arc::clone(render_data), + }) + .ok(); } return None; } @@ -2613,7 +2607,7 @@ impl OutlinePanel { } fn entry_name(&self, worktree_id: &WorktreeId, entry: &Entry, cx: &App) -> String { - let name = match self.project.read(cx).worktree_for_id(*worktree_id, cx) { + match self.project.read(cx).worktree_for_id(*worktree_id, cx) { Some(worktree) => { let worktree = worktree.read(cx); match worktree.snapshot().root_entry() { @@ -2634,8 +2628,7 @@ impl OutlinePanel { } } None => file_name(entry.path.as_ref()), - }; - name + } } fn update_fs_entries( @@ -2670,7 +2663,8 @@ impl OutlinePanel { new_collapsed_entries = outline_panel.collapsed_entries.clone(); new_unfolded_dirs = outline_panel.unfolded_dirs.clone(); let multi_buffer_snapshot = active_multi_buffer.read(cx).snapshot(cx); - let buffer_excerpts = multi_buffer_snapshot.excerpts().fold( + + multi_buffer_snapshot.excerpts().fold( HashMap::default(), |mut buffer_excerpts, (excerpt_id, buffer_snapshot, excerpt_range)| { let buffer_id = buffer_snapshot.remote_id(); @@ -2717,8 +2711,7 @@ impl OutlinePanel { ); buffer_excerpts }, - ); - buffer_excerpts + ) }) else { return; }; @@ -2817,11 +2810,12 @@ impl OutlinePanel { let new_entry_added = entries_to_add .insert(current_entry.id, current_entry) .is_none(); - if new_entry_added && traversal.back_to_parent() { - if let Some(parent_entry) = traversal.entry() { - current_entry = parent_entry.to_owned(); - continue; - } + if new_entry_added + && traversal.back_to_parent() + && let Some(parent_entry) = traversal.entry() + { + current_entry = parent_entry.to_owned(); + continue; } break; } @@ -2862,18 +2856,17 @@ impl OutlinePanel { entries .into_iter() .filter_map(|entry| { - if auto_fold_dirs { - if let Some(parent) = entry.path.parent() { - let children = new_children_count - .entry(worktree_id) - .or_default() - .entry(Arc::from(parent)) - .or_default(); - if entry.is_dir() { - children.dirs += 1; - } else { - children.files += 1; - } + if auto_fold_dirs && let Some(parent) = entry.path.parent() + { + let children = new_children_count + .entry(worktree_id) + .or_default() + .entry(Arc::from(parent)) + .or_default(); + if entry.is_dir() { + children.dirs += 1; + } else { + children.files += 1; } } @@ -2940,7 +2933,7 @@ impl OutlinePanel { .map(|(parent_dir_id, _)| { new_unfolded_dirs .get(&directory.worktree_id) - .map_or(true, |unfolded_dirs| { + .is_none_or(|unfolded_dirs| { unfolded_dirs .contains(parent_dir_id) }) @@ -3393,30 +3386,29 @@ impl OutlinePanel { { excerpt.outlines = ExcerptOutlines::Outlines(fetched_outlines); - if let Some(default_depth) = pending_default_depth { - if let ExcerptOutlines::Outlines(outlines) = + if let Some(default_depth) = pending_default_depth + && let ExcerptOutlines::Outlines(outlines) = &excerpt.outlines - { - outlines - .iter() - .filter(|outline| { - (default_depth == 0 - || outline.depth >= default_depth) - && outlines_with_children.contains(&( - outline.range.clone(), - outline.depth, - )) - }) - .for_each(|outline| { - outline_panel.collapsed_entries.insert( - CollapsedEntry::Outline( - buffer_id, - excerpt_id, - outline.range.clone(), - ), - ); - }); - } + { + outlines + .iter() + .filter(|outline| { + (default_depth == 0 + || outline.depth >= default_depth) + && outlines_with_children.contains(&( + outline.range.clone(), + outline.depth, + )) + }) + .for_each(|outline| { + outline_panel.collapsed_entries.insert( + CollapsedEntry::Outline( + buffer_id, + excerpt_id, + outline.range.clone(), + ), + ); + }); } // Even if no outlines to check, we still need to update cached entries @@ -3432,9 +3424,8 @@ impl OutlinePanel { } fn is_singleton_active(&self, cx: &App) -> bool { - self.active_editor().map_or(false, |active_editor| { - active_editor.read(cx).buffer().read(cx).is_singleton() - }) + self.active_editor() + .is_some_and(|active_editor| active_editor.read(cx).buffer().read(cx).is_singleton()) } fn invalidate_outlines(&mut self, ids: &[ExcerptId]) { @@ -3595,10 +3586,9 @@ impl OutlinePanel { .update_in(cx, |outline_panel, window, cx| { outline_panel.cached_entries = new_cached_entries; outline_panel.max_width_item_index = max_width_item_index; - if outline_panel.selected_entry.is_invalidated() - || matches!(outline_panel.selected_entry, SelectedEntry::None) - { - if let Some(new_selected_entry) = + if (outline_panel.selected_entry.is_invalidated() + || matches!(outline_panel.selected_entry, SelectedEntry::None)) + && let Some(new_selected_entry) = outline_panel.active_editor().and_then(|active_editor| { outline_panel.location_for_editor_selection( &active_editor, @@ -3606,9 +3596,8 @@ impl OutlinePanel { cx, ) }) - { - outline_panel.select_entry(new_selected_entry, false, window, cx); - } + { + outline_panel.select_entry(new_selected_entry, false, window, cx); } outline_panel.autoscroll(cx); @@ -3654,7 +3643,7 @@ impl OutlinePanel { let is_root = project .read(cx) .worktree_for_id(directory_entry.worktree_id, cx) - .map_or(false, |worktree| { + .is_some_and(|worktree| { worktree.read(cx).root_entry() == Some(&directory_entry.entry) }); let folded = auto_fold_dirs @@ -3662,7 +3651,7 @@ impl OutlinePanel { && outline_panel .unfolded_dirs .get(&directory_entry.worktree_id) - .map_or(true, |unfolded_dirs| { + .is_none_or(|unfolded_dirs| { !unfolded_dirs.contains(&directory_entry.entry.id) }); let fs_depth = outline_panel @@ -3742,7 +3731,7 @@ impl OutlinePanel { .iter() .rev() .nth(folded_dirs.entries.len() + 1) - .map_or(true, |parent| parent.expanded); + .is_none_or(|parent| parent.expanded); if start_of_collapsed_dir_sequence || parent_expanded || query.is_some() @@ -3802,7 +3791,7 @@ impl OutlinePanel { .iter() .all(|entry| entry.path != parent.path) }) - .map_or(true, |parent| parent.expanded); + .is_none_or(|parent| parent.expanded); if !is_singleton && (parent_expanded || query.is_some()) { outline_panel.push_entry( &mut generation_state, @@ -3827,7 +3816,7 @@ impl OutlinePanel { .iter() .all(|entry| entry.path != parent.path) }) - .map_or(true, |parent| parent.expanded); + .is_none_or(|parent| parent.expanded); if !is_singleton && (parent_expanded || query.is_some()) { outline_panel.push_entry( &mut generation_state, @@ -3905,19 +3894,19 @@ impl OutlinePanel { } else { None }; - if let Some((buffer_id, entry_excerpts)) = excerpts_to_consider { - if !active_editor.read(cx).is_buffer_folded(buffer_id, cx) { - outline_panel.add_excerpt_entries( - &mut generation_state, - buffer_id, - entry_excerpts, - depth, - track_matches, - is_singleton, - query.as_deref(), - cx, - ); - } + if let Some((buffer_id, entry_excerpts)) = excerpts_to_consider + && !active_editor.read(cx).is_buffer_folded(buffer_id, cx) + { + outline_panel.add_excerpt_entries( + &mut generation_state, + buffer_id, + entry_excerpts, + depth, + track_matches, + is_singleton, + query.as_deref(), + cx, + ); } } } @@ -3948,7 +3937,7 @@ impl OutlinePanel { .iter() .all(|entry| entry.path != parent.path) }) - .map_or(true, |parent| parent.expanded); + .is_none_or(|parent| parent.expanded); if parent_expanded || query.is_some() { outline_panel.push_entry( &mut generation_state, @@ -4388,15 +4377,15 @@ impl OutlinePanel { }) .filter(|(match_range, _)| { let editor = active_editor.read(cx); - if let Some(buffer_id) = match_range.start.buffer_id { - if editor.is_buffer_folded(buffer_id, cx) { - return false; - } + if let Some(buffer_id) = match_range.start.buffer_id + && editor.is_buffer_folded(buffer_id, cx) + { + return false; } - if let Some(buffer_id) = match_range.start.buffer_id { - if editor.is_buffer_folded(buffer_id, cx) { - return false; - } + if let Some(buffer_id) = match_range.start.buffer_id + && editor.is_buffer_folded(buffer_id, cx) + { + return false; } true }); @@ -4428,7 +4417,7 @@ impl OutlinePanel { } fn should_replace_active_item(&self, new_active_item: &dyn ItemHandle) -> bool { - self.active_item().map_or(true, |active_item| { + self.active_item().is_none_or(|active_item| { !self.pinned && active_item.item_id() != new_active_item.item_id() }) } @@ -4440,16 +4429,14 @@ impl OutlinePanel { cx: &mut Context<Self>, ) { self.pinned = !self.pinned; - if !self.pinned { - if let Some((active_item, active_editor)) = self + if !self.pinned + && let Some((active_item, active_editor)) = self .workspace .upgrade() .and_then(|workspace| workspace_active_editor(workspace.read(cx), cx)) - { - if self.should_replace_active_item(active_item.as_ref()) { - self.replace_active_editor(active_item, active_editor, window, cx); - } - } + && self.should_replace_active_item(active_item.as_ref()) + { + self.replace_active_editor(active_item, active_editor, window, cx); } cx.notify(); @@ -4658,7 +4645,7 @@ impl OutlinePanel { .with_compute_indents_fn(cx.entity(), |outline_panel, range, _, _| { let entries = outline_panel.cached_entries.get(range); if let Some(entries) = entries { - entries.into_iter().map(|item| item.depth).collect() + entries.iter().map(|item| item.depth).collect() } else { smallvec::SmallVec::new() } @@ -4910,24 +4897,23 @@ impl Panel for OutlinePanel { let old_active = outline_panel.active; outline_panel.active = active; if old_active != active { - if active { - if let Some((active_item, active_editor)) = + if active + && let Some((active_item, active_editor)) = outline_panel.workspace.upgrade().and_then(|workspace| { workspace_active_editor(workspace.read(cx), cx) }) - { - if outline_panel.should_replace_active_item(active_item.as_ref()) { - outline_panel.replace_active_editor( - active_item, - active_editor, - window, - cx, - ); - } else { - outline_panel.update_fs_entries(active_editor, None, window, cx) - } - return; + { + if outline_panel.should_replace_active_item(active_item.as_ref()) { + outline_panel.replace_active_editor( + active_item, + active_editor, + window, + cx, + ); + } else { + outline_panel.update_fs_entries(active_editor, None, window, cx) } + return; } if !outline_panel.pinned { @@ -5153,8 +5139,8 @@ fn subscribe_for_editor_events( }) .copied(), ); - if !ignore_selections_change { - if let Some(entry_to_select) = latest_unfolded_buffer_id + if !ignore_selections_change + && let Some(entry_to_select) = latest_unfolded_buffer_id .or(latest_folded_buffer_id) .and_then(|toggled_buffer_id| { outline_panel.fs_entries.iter().find_map( @@ -5178,9 +5164,8 @@ fn subscribe_for_editor_events( ) }) .map(PanelEntry::Fs) - { - outline_panel.select_entry(entry_to_select, true, window, cx); - } + { + outline_panel.select_entry(entry_to_select, true, window, cx); } outline_panel.update_fs_entries(editor.clone(), debounce, window, cx); @@ -5332,7 +5317,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5348,7 +5333,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5366,7 +5351,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5403,7 +5388,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5417,7 +5402,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5436,7 +5421,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5464,7 +5449,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5552,7 +5537,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, None, cx, @@ -5575,7 +5560,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, None, cx, @@ -5601,7 +5586,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, None, cx, @@ -5707,7 +5692,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5730,7 +5715,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5767,7 +5752,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5804,7 +5789,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5907,7 +5892,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5933,7 +5918,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5957,7 +5942,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -5978,7 +5963,7 @@ mod tests { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6066,7 +6051,7 @@ struct OutlineEntryExcerpt { assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6093,7 +6078,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6120,7 +6105,7 @@ outline: struct OutlineEntryExcerpt <==== selected assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6147,7 +6132,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6174,7 +6159,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6201,7 +6186,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6228,7 +6213,7 @@ outline: struct OutlineEntryExcerpt <==== selected assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6255,7 +6240,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6282,7 +6267,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6309,7 +6294,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6336,7 +6321,7 @@ outline: struct OutlineEntryExcerpt <==== selected assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6442,7 +6427,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6479,7 +6464,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6507,7 +6492,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6539,7 +6524,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6570,7 +6555,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -6698,7 +6683,7 @@ outline: struct OutlineEntryExcerpt .render_data .get_or_init(|| SearchData::new( &search_entry.match_range, - &multi_buffer_snapshot + multi_buffer_snapshot )) .context_text ) @@ -7089,7 +7074,7 @@ outline: struct OutlineEntryExcerpt assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -7148,7 +7133,7 @@ outline: fn main()" assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -7172,7 +7157,7 @@ outline: fn main()" assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -7237,7 +7222,7 @@ outline: fn main()" assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -7378,7 +7363,7 @@ outline: fn main()" assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -7416,7 +7401,7 @@ outline: fn main()" assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -7450,7 +7435,7 @@ outline: fn main()" assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, @@ -7482,7 +7467,7 @@ outline: fn main()" assert_eq!( display_entries( &project, - &snapshot(&outline_panel, cx), + &snapshot(outline_panel, cx), &outline_panel.cached_entries, outline_panel.selected_entry(), cx, diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 33320e6845..32e39d466f 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -119,7 +119,7 @@ impl Prettier { None } }).any(|workspace_definition| { - workspace_definition == subproject_path.to_string_lossy() || PathMatcher::new(&[workspace_definition]).ok().map_or(false, |path_matcher| path_matcher.is_match(subproject_path)) + workspace_definition == subproject_path.to_string_lossy() || PathMatcher::new(&[workspace_definition]).ok().is_some_and(|path_matcher| path_matcher.is_match(subproject_path)) }) { anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Path {path_to_check:?} is the workspace root for project in {closest_package_json_path:?}, but it has no prettier installed"); log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {closest_package_json_path:?}"); @@ -185,11 +185,11 @@ impl Prettier { .metadata(&ignore_path) .await .with_context(|| format!("fetching metadata for {ignore_path:?}"))? + && !metadata.is_dir + && !metadata.is_symlink { - if !metadata.is_dir && !metadata.is_symlink { - log::info!("Found prettier ignore at {ignore_path:?}"); - return Ok(ControlFlow::Continue(Some(path_to_check))); - } + log::info!("Found prettier ignore at {ignore_path:?}"); + return Ok(ControlFlow::Continue(Some(path_to_check))); } match &closest_package_json_path { None => closest_package_json_path = Some(path_to_check.clone()), @@ -217,19 +217,19 @@ impl Prettier { workspace_definition == subproject_path.to_string_lossy() || PathMatcher::new(&[workspace_definition]) .ok() - .map_or(false, |path_matcher| { + .is_some_and(|path_matcher| { path_matcher.is_match(subproject_path) }) }) { let workspace_ignore = path_to_check.join(".prettierignore"); - if let Some(metadata) = fs.metadata(&workspace_ignore).await? { - if !metadata.is_dir { - log::info!( - "Found prettier ignore at workspace root {workspace_ignore:?}" - ); - return Ok(ControlFlow::Continue(Some(path_to_check))); - } + if let Some(metadata) = fs.metadata(&workspace_ignore).await? + && !metadata.is_dir + { + log::info!( + "Found prettier ignore at workspace root {workspace_ignore:?}" + ); + return Ok(ControlFlow::Continue(Some(path_to_check))); } } } @@ -549,18 +549,16 @@ async fn read_package_json( .metadata(&possible_package_json) .await .with_context(|| format!("fetching metadata for package json {possible_package_json:?}"))? + && !package_json_metadata.is_dir + && !package_json_metadata.is_symlink { - if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { - let package_json_contents = fs - .load(&possible_package_json) - .await - .with_context(|| format!("reading {possible_package_json:?} file contents"))?; - return serde_json::from_str::<HashMap<String, serde_json::Value>>( - &package_json_contents, - ) + let package_json_contents = fs + .load(&possible_package_json) + .await + .with_context(|| format!("reading {possible_package_json:?} file contents"))?; + return serde_json::from_str::<HashMap<String, serde_json::Value>>(&package_json_contents) .map(Some) .with_context(|| format!("parsing {possible_package_json:?} file contents")); - } } Ok(None) } diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index b8101e14f3..d365089377 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -234,7 +234,7 @@ impl RemoteBufferStore { } } } - return Ok(None); + Ok(None) } pub fn incomplete_buffer_ids(&self) -> Vec<BufferId> { @@ -413,13 +413,10 @@ impl LocalBufferStore { cx: &mut Context<BufferStore>, ) { cx.subscribe(worktree, |this, worktree, event, cx| { - if worktree.read(cx).is_local() { - match event { - worktree::Event::UpdatedEntries(changes) => { - Self::local_worktree_entries_changed(this, &worktree, changes, cx); - } - _ => {} - } + if worktree.read(cx).is_local() + && let worktree::Event::UpdatedEntries(changes) = event + { + Self::local_worktree_entries_changed(this, &worktree, changes, cx); } }) .detach(); @@ -947,10 +944,9 @@ impl BufferStore { } pub fn get_by_path(&self, path: &ProjectPath) -> Option<Entity<Buffer>> { - self.path_to_buffer_id.get(path).and_then(|buffer_id| { - let buffer = self.get(*buffer_id); - buffer - }) + self.path_to_buffer_id + .get(path) + .and_then(|buffer_id| self.get(*buffer_id)) } pub fn get(&self, buffer_id: BufferId) -> Option<Entity<Buffer>> { @@ -1094,10 +1090,10 @@ impl BufferStore { .collect::<Vec<_>>() })?; for buffer_task in buffers { - if let Some(buffer) = buffer_task.await.log_err() { - if tx.send(buffer).await.is_err() { - return anyhow::Ok(()); - } + if let Some(buffer) = buffer_task.await.log_err() + && tx.send(buffer).await.is_err() + { + return anyhow::Ok(()); } } } @@ -1173,11 +1169,11 @@ impl BufferStore { buffer_id: BufferId, handle: OpenLspBufferHandle, ) { - if let Some(shared_buffers) = self.shared_buffers.get_mut(&peer_id) { - if let Some(buffer) = shared_buffers.get_mut(&buffer_id) { - buffer.lsp_handle = Some(handle); - return; - } + if let Some(shared_buffers) = self.shared_buffers.get_mut(&peer_id) + && let Some(buffer) = shared_buffers.get_mut(&buffer_id) + { + buffer.lsp_handle = Some(handle); + return; } debug_panic!("tried to register shared lsp handle, but buffer was not shared") } @@ -1313,10 +1309,7 @@ impl BufferStore { let new_path = file.path.clone(); buffer.file_updated(Arc::new(file), cx); - if old_file - .as_ref() - .map_or(true, |old| *old.path() != new_path) - { + if old_file.as_ref().is_none_or(|old| *old.path() != new_path) { Some(old_file) } else { None @@ -1345,7 +1338,7 @@ impl BufferStore { mut cx: AsyncApp, ) -> Result<proto::BufferSaved> { let buffer_id = BufferId::new(envelope.payload.buffer_id)?; - let (buffer, project_id) = this.read_with(&mut cx, |this, _| { + let (buffer, project_id) = this.read_with(&cx, |this, _| { anyhow::Ok(( this.get_existing(buffer_id)?, this.downstream_client @@ -1359,7 +1352,7 @@ impl BufferStore { buffer.wait_for_version(deserialize_version(&envelope.payload.version)) })? .await?; - let buffer_id = buffer.read_with(&mut cx, |buffer, _| buffer.remote_id())?; + let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?; if let Some(new_path) = envelope.payload.new_path { let new_path = ProjectPath::from_proto(new_path); @@ -1372,7 +1365,7 @@ impl BufferStore { .await?; } - buffer.read_with(&mut cx, |buffer, _| proto::BufferSaved { + buffer.read_with(&cx, |buffer, _| proto::BufferSaved { project_id, buffer_id: buffer_id.into(), version: serialize_version(buffer.saved_version()), @@ -1388,14 +1381,14 @@ impl BufferStore { let peer_id = envelope.sender_id; let buffer_id = BufferId::new(envelope.payload.buffer_id)?; this.update(&mut cx, |this, cx| { - if let Some(shared) = this.shared_buffers.get_mut(&peer_id) { - if shared.remove(&buffer_id).is_some() { - cx.emit(BufferStoreEvent::SharedBufferClosed(peer_id, buffer_id)); - if shared.is_empty() { - this.shared_buffers.remove(&peer_id); - } - return; + if let Some(shared) = this.shared_buffers.get_mut(&peer_id) + && shared.remove(&buffer_id).is_some() + { + cx.emit(BufferStoreEvent::SharedBufferClosed(peer_id, buffer_id)); + if shared.is_empty() { + this.shared_buffers.remove(&peer_id); } + return; } debug_panic!( "peer_id {} closed buffer_id {} which was either not open or already closed", diff --git a/crates/project/src/color_extractor.rs b/crates/project/src/color_extractor.rs index 5473da88af..6e9907e30b 100644 --- a/crates/project/src/color_extractor.rs +++ b/crates/project/src/color_extractor.rs @@ -4,8 +4,8 @@ use gpui::{Hsla, Rgba}; use lsp::{CompletionItem, Documentation}; use regex::{Regex, RegexBuilder}; -const HEX: &'static str = r#"(#(?:[\da-fA-F]{3}){1,2})"#; -const RGB_OR_HSL: &'static str = r#"(rgba?|hsla?)\(\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*(?:,\s*(1|0?\.\d+))?\s*\)"#; +const HEX: &str = r#"(#(?:[\da-fA-F]{3}){1,2})"#; +const RGB_OR_HSL: &str = r#"(rgba?|hsla?)\(\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*(?:,\s*(1|0?\.\d+))?\s*\)"#; static RELAXED_HEX_REGEX: LazyLock<Regex> = LazyLock::new(|| { RegexBuilder::new(HEX) @@ -102,7 +102,7 @@ fn parse(str: &str, mode: ParseMode) -> Option<Hsla> { }; } - return None; + None } fn parse_component(value: &str, max: f32) -> Option<f32> { @@ -141,7 +141,7 @@ mod tests { use gpui::rgba; use lsp::{CompletionItem, CompletionItemKind}; - pub const COLOR_TABLE: &[(&'static str, Option<u32>)] = &[ + pub const COLOR_TABLE: &[(&str, Option<u32>)] = &[ // -- Invalid -- // Invalid hex ("f0f", None), diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index c96ab4e8f3..e826f44b7b 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -368,7 +368,7 @@ impl ContextServerStore { } pub fn restart_server(&mut self, id: &ContextServerId, cx: &mut Context<Self>) -> Result<()> { - if let Some(state) = self.servers.get(&id) { + if let Some(state) = self.servers.get(id) { let configuration = state.configuration(); self.stop_server(&state.server().id(), cx)?; @@ -397,9 +397,8 @@ impl ContextServerStore { let server = server.clone(); let configuration = configuration.clone(); async move |this, cx| { - match server.clone().start(&cx).await { + match server.clone().start(cx).await { Ok(_) => { - log::info!("Started {} context server", id); debug_assert!(server.client().is_some()); this.update(cx, |this, cx| { @@ -588,7 +587,7 @@ impl ContextServerStore { for server_id in this.servers.keys() { // All servers that are not in desired_servers should be removed from the store. // This can happen if the user removed a server from the context server settings. - if !configured_servers.contains_key(&server_id) { + if !configured_servers.contains_key(server_id) { if disabled_servers.contains_key(&server_id.0) { servers_to_stop.insert(server_id.clone()); } else { @@ -642,8 +641,8 @@ mod tests { #[gpui::test] async fn test_context_server_status(cx: &mut TestAppContext) { - const SERVER_1_ID: &'static str = "mcp-1"; - const SERVER_2_ID: &'static str = "mcp-2"; + const SERVER_1_ID: &str = "mcp-1"; + const SERVER_2_ID: &str = "mcp-2"; let (_fs, project) = setup_context_server_test( cx, @@ -722,8 +721,8 @@ mod tests { #[gpui::test] async fn test_context_server_status_events(cx: &mut TestAppContext) { - const SERVER_1_ID: &'static str = "mcp-1"; - const SERVER_2_ID: &'static str = "mcp-2"; + const SERVER_1_ID: &str = "mcp-1"; + const SERVER_2_ID: &str = "mcp-2"; let (_fs, project) = setup_context_server_test( cx, @@ -784,7 +783,7 @@ mod tests { #[gpui::test(iterations = 25)] async fn test_context_server_concurrent_starts(cx: &mut TestAppContext) { - const SERVER_1_ID: &'static str = "mcp-1"; + const SERVER_1_ID: &str = "mcp-1"; let (_fs, project) = setup_context_server_test( cx, @@ -845,8 +844,8 @@ mod tests { #[gpui::test] async fn test_context_server_maintain_servers_loop(cx: &mut TestAppContext) { - const SERVER_1_ID: &'static str = "mcp-1"; - const SERVER_2_ID: &'static str = "mcp-2"; + const SERVER_1_ID: &str = "mcp-1"; + const SERVER_2_ID: &str = "mcp-2"; let server_1_id = ContextServerId(SERVER_1_ID.into()); let server_2_id = ContextServerId(SERVER_2_ID.into()); @@ -1084,7 +1083,7 @@ mod tests { #[gpui::test] async fn test_context_server_enabled_disabled(cx: &mut TestAppContext) { - const SERVER_1_ID: &'static str = "mcp-1"; + const SERVER_1_ID: &str = "mcp-1"; let server_1_id = ContextServerId(SERVER_1_ID.into()); diff --git a/crates/project/src/context_server_store/extension.rs b/crates/project/src/context_server_store/extension.rs index 1eb0fe7da1..2a3a0c2e4b 100644 --- a/crates/project/src/context_server_store/extension.rs +++ b/crates/project/src/context_server_store/extension.rs @@ -63,7 +63,7 @@ impl registry::ContextServerDescriptor for ContextServerDescriptor { .await?; command.command = extension.path_from_extension(&command.command); - log::info!("loaded command for context server {id}: {command:?}"); + log::debug!("loaded command for context server {id}: {command:?}"); Ok(ContextServerCommand { path: command.command, diff --git a/crates/project/src/debugger/breakpoint_store.rs b/crates/project/src/debugger/breakpoint_store.rs index 025dca4100..00fcc7e69f 100644 --- a/crates/project/src/debugger/breakpoint_store.rs +++ b/crates/project/src/debugger/breakpoint_store.rs @@ -267,7 +267,7 @@ impl BreakpointStore { message: TypedEnvelope<proto::ToggleBreakpoint>, mut cx: AsyncApp, ) -> Result<proto::Ack> { - let breakpoints = this.read_with(&mut cx, |this, _| this.breakpoint_store())?; + let breakpoints = this.read_with(&cx, |this, _| this.breakpoint_store())?; let path = this .update(&mut cx, |this, cx| { this.project_path_for_absolute_path(message.payload.path.as_ref(), cx) @@ -317,8 +317,8 @@ impl BreakpointStore { .iter() .filter_map(|breakpoint| { breakpoint.bp.bp.to_proto( - &path, - &breakpoint.position(), + path, + breakpoint.position(), &breakpoint.session_state, ) }) @@ -623,12 +623,11 @@ impl BreakpointStore { file_breakpoints.breakpoints.iter().filter_map({ let range = range.clone(); move |bp| { - if let Some(range) = &range { - if bp.position().cmp(&range.start, buffer_snapshot).is_lt() - || bp.position().cmp(&range.end, buffer_snapshot).is_gt() - { - return None; - } + if let Some(range) = &range + && (bp.position().cmp(&range.start, buffer_snapshot).is_lt() + || bp.position().cmp(&range.end, buffer_snapshot).is_gt()) + { + return None; } let session_state = active_session_id .and_then(|id| bp.session_state.get(&id)) @@ -753,7 +752,7 @@ impl BreakpointStore { .iter() .map(|breakpoint| { let position = snapshot - .summary_for_anchor::<PointUtf16>(&breakpoint.position()) + .summary_for_anchor::<PointUtf16>(breakpoint.position()) .row; let breakpoint = &breakpoint.bp; SourceBreakpoint { @@ -832,7 +831,6 @@ impl BreakpointStore { new_breakpoints.insert(path, breakpoints_for_file); } this.update(cx, |this, cx| { - log::info!("Finish deserializing breakpoints & initializing breakpoint store"); for (path, count) in new_breakpoints.iter().map(|(path, bp_in_file)| { (path.to_string_lossy(), bp_in_file.breakpoints.len()) }) { diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 6f834b5dc0..382e83587a 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -215,7 +215,7 @@ impl DapStore { dap_settings.and_then(|s| s.binary.as_ref().map(PathBuf::from)); let user_args = dap_settings.map(|s| s.args.clone()); - let delegate = self.delegate(&worktree, console, cx); + let delegate = self.delegate(worktree, console, cx); let cwd: Arc<Path> = worktree.read(cx).abs_path().as_ref().into(); cx.spawn(async move |this, cx| { @@ -470,9 +470,8 @@ impl DapStore { session_id: impl Borrow<SessionId>, ) -> Option<Entity<session::Session>> { let session_id = session_id.borrow(); - let client = self.sessions.get(session_id).cloned(); - client + self.sessions.get(session_id).cloned() } pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> { self.sessions.values() @@ -902,7 +901,7 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate { } fn worktree_root_path(&self) -> &Path { - &self.worktree.abs_path() + self.worktree.abs_path() } fn http_client(&self) -> Arc<dyn HttpClient> { self.http_client.clone() diff --git a/crates/project/src/debugger/locators/cargo.rs b/crates/project/src/debugger/locators/cargo.rs index fa265dae58..3e28fac8af 100644 --- a/crates/project/src/debugger/locators/cargo.rs +++ b/crates/project/src/debugger/locators/cargo.rs @@ -146,7 +146,7 @@ impl DapLocator for CargoLocator { let is_test = build_config .args .first() - .map_or(false, |arg| arg == "test" || arg == "t"); + .is_some_and(|arg| arg == "test" || arg == "t"); let executables = output .lines() @@ -187,12 +187,12 @@ impl DapLocator for CargoLocator { .cloned(); } let executable = { - if let Some(ref name) = test_name.as_ref().and_then(|name| { + if let Some(name) = test_name.as_ref().and_then(|name| { name.strip_prefix('$') .map(|name| build_config.env.get(name)) .unwrap_or(Some(name)) }) { - find_best_executable(&executables, &name).await + find_best_executable(&executables, name).await } else { None } diff --git a/crates/project/src/debugger/locators/go.rs b/crates/project/src/debugger/locators/go.rs index 61436fce8f..eec06084ec 100644 --- a/crates/project/src/debugger/locators/go.rs +++ b/crates/project/src/debugger/locators/go.rs @@ -174,7 +174,7 @@ impl DapLocator for GoLocator { request: "launch".to_string(), mode: "test".to_string(), program, - args: args, + args, build_flags, cwd: build_config.cwd.clone(), env: build_config.env.clone(), @@ -185,7 +185,7 @@ impl DapLocator for GoLocator { label: resolved_label.to_string().into(), adapter: adapter.0.clone(), build: None, - config: config, + config, tcp_connection: None, }) } @@ -220,7 +220,7 @@ impl DapLocator for GoLocator { request: "launch".to_string(), mode: "debug".to_string(), program, - args: args, + args, build_flags, }) .unwrap(); diff --git a/crates/project/src/debugger/locators/python.rs b/crates/project/src/debugger/locators/python.rs index 3de1281aed..71efbb75b9 100644 --- a/crates/project/src/debugger/locators/python.rs +++ b/crates/project/src/debugger/locators/python.rs @@ -28,9 +28,7 @@ impl DapLocator for PythonLocator { let valid_program = build_config.command.starts_with("$ZED_") || Path::new(&build_config.command) .file_name() - .map_or(false, |name| { - name.to_str().is_some_and(|path| path.starts_with("python")) - }); + .is_some_and(|name| name.to_str().is_some_and(|path| path.starts_with("python"))); if !valid_program || build_config.args.iter().any(|arg| arg == "-c") { // We cannot debug selections. return None; diff --git a/crates/project/src/debugger/memory.rs b/crates/project/src/debugger/memory.rs index fec3c344c5..a8729a8ff4 100644 --- a/crates/project/src/debugger/memory.rs +++ b/crates/project/src/debugger/memory.rs @@ -318,19 +318,18 @@ impl Iterator for MemoryIterator { return None; } if let Some((current_page_address, current_memory_chunk)) = self.current_known_page.as_mut() + && current_page_address.0 <= self.start { - if current_page_address.0 <= self.start { - if let Some(next_cell) = current_memory_chunk.next() { - self.start += 1; - return Some(next_cell); - } else { - self.current_known_page.take(); - } + if let Some(next_cell) = current_memory_chunk.next() { + self.start += 1; + return Some(next_cell); + } else { + self.current_known_page.take(); } } if !self.fetch_next_page() { self.start += 1; - return Some(MemoryCell(None)); + Some(MemoryCell(None)) } else { self.next() } diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index d9c28df497..cd792877b6 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -226,7 +226,7 @@ impl RunningMode { fn unset_breakpoints_from_paths(&self, paths: &Vec<Arc<Path>>, cx: &mut App) -> Task<()> { let tasks: Vec<_> = paths - .into_iter() + .iter() .map(|path| { self.request(dap_command::SetBreakpoints { source: client_source(path), @@ -431,7 +431,7 @@ impl RunningMode { let should_send_exception_breakpoints = capabilities .exception_breakpoint_filters .as_ref() - .map_or(false, |filters| !filters.is_empty()) + .is_some_and(|filters| !filters.is_empty()) || !configuration_done_supported; let supports_exception_filters = capabilities .supports_exception_filter_options @@ -508,13 +508,12 @@ impl RunningMode { .ok(); } - let ret = if configuration_done_supported { + if configuration_done_supported { this.request(ConfigurationDone {}) } else { Task::ready(Ok(())) } - .await; - ret + .await } }); @@ -710,9 +709,7 @@ where T: LocalDapCommand + PartialEq + Eq + Hash, { fn dyn_eq(&self, rhs: &dyn CacheableCommand) -> bool { - (rhs as &dyn Any) - .downcast_ref::<Self>() - .map_or(false, |rhs| self == rhs) + (rhs as &dyn Any).downcast_ref::<Self>() == Some(self) } fn dyn_hash(&self, mut hasher: &mut dyn Hasher) { @@ -841,7 +838,7 @@ impl Session { }) .detach(); - let this = Self { + Self { mode: SessionState::Booting(None), id: session_id, child_session_ids: HashSet::default(), @@ -870,9 +867,7 @@ impl Session { task_context, memory: memory::Memory::new(), quirks, - }; - - this + } }) } @@ -1085,7 +1080,7 @@ impl Session { }) .detach(); - return tx; + tx } pub fn is_started(&self) -> bool { @@ -1630,7 +1625,7 @@ impl Session { + 'static, cx: &mut Context<Self>, ) -> Task<Option<T::Response>> { - if !T::is_supported(&capabilities) { + if !T::is_supported(capabilities) { log::warn!( "Attempted to send a DAP request that isn't supported: {:?}", request @@ -1688,7 +1683,7 @@ impl Session { self.requests .entry((&*key.0 as &dyn Any).type_id()) .and_modify(|request_map| { - request_map.remove(&key); + request_map.remove(key); }); } diff --git a/crates/project/src/environment.rs b/crates/project/src/environment.rs index 7379a7ef72..d109e307a8 100644 --- a/crates/project/src/environment.rs +++ b/crates/project/src/environment.rs @@ -198,7 +198,7 @@ async fn load_directory_shell_environment( ); }; - load_shell_environment(&dir, load_direnv).await + load_shell_environment(dir, load_direnv).await } Err(err) => ( None, diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 3163a10239..edc6b00a7b 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -561,7 +561,7 @@ impl GitStore { pub fn active_repository(&self) -> Option<Entity<Repository>> { self.active_repo_id .as_ref() - .map(|id| self.repositories[&id].clone()) + .map(|id| self.repositories[id].clone()) } pub fn open_unstaged_diff( @@ -570,23 +570,22 @@ impl GitStore { cx: &mut Context<Self>, ) -> Task<Result<Entity<BufferDiff>>> { let buffer_id = buffer.read(cx).remote_id(); - if let Some(diff_state) = self.diffs.get(&buffer_id) { - if let Some(unstaged_diff) = diff_state + if let Some(diff_state) = self.diffs.get(&buffer_id) + && let Some(unstaged_diff) = diff_state .read(cx) .unstaged_diff .as_ref() .and_then(|weak| weak.upgrade()) + { + if let Some(task) = + diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation()) { - if let Some(task) = - diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation()) - { - return cx.background_executor().spawn(async move { - task.await; - Ok(unstaged_diff) - }); - } - return Task::ready(Ok(unstaged_diff)); + return cx.background_executor().spawn(async move { + task.await; + Ok(unstaged_diff) + }); } + return Task::ready(Ok(unstaged_diff)); } let Some((repo, repo_path)) = @@ -627,23 +626,22 @@ impl GitStore { ) -> Task<Result<Entity<BufferDiff>>> { let buffer_id = buffer.read(cx).remote_id(); - if let Some(diff_state) = self.diffs.get(&buffer_id) { - if let Some(uncommitted_diff) = diff_state + if let Some(diff_state) = self.diffs.get(&buffer_id) + && let Some(uncommitted_diff) = diff_state .read(cx) .uncommitted_diff .as_ref() .and_then(|weak| weak.upgrade()) + { + if let Some(task) = + diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation()) { - if let Some(task) = - diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation()) - { - return cx.background_executor().spawn(async move { - task.await; - Ok(uncommitted_diff) - }); - } - return Task::ready(Ok(uncommitted_diff)); + return cx.background_executor().spawn(async move { + task.await; + Ok(uncommitted_diff) + }); } + return Task::ready(Ok(uncommitted_diff)); } let Some((repo, repo_path)) = @@ -764,29 +762,26 @@ impl GitStore { log::debug!("open conflict set"); let buffer_id = buffer.read(cx).remote_id(); - if let Some(git_state) = self.diffs.get(&buffer_id) { - if let Some(conflict_set) = git_state + if let Some(git_state) = self.diffs.get(&buffer_id) + && let Some(conflict_set) = git_state .read(cx) .conflict_set .as_ref() .and_then(|weak| weak.upgrade()) - { - let conflict_set = conflict_set.clone(); - let buffer_snapshot = buffer.read(cx).text_snapshot(); + { + let conflict_set = conflict_set.clone(); + let buffer_snapshot = buffer.read(cx).text_snapshot(); - git_state.update(cx, |state, cx| { - let _ = state.reparse_conflict_markers(buffer_snapshot, cx); - }); + git_state.update(cx, |state, cx| { + let _ = state.reparse_conflict_markers(buffer_snapshot, cx); + }); - return conflict_set; - } + return conflict_set; } let is_unmerged = self .repository_and_path_for_buffer_id(buffer_id, cx) - .map_or(false, |(repo, path)| { - repo.read(cx).snapshot.has_conflict(&path) - }); + .is_some_and(|(repo, path)| repo.read(cx).snapshot.has_conflict(&path)); let git_store = cx.weak_entity(); let buffer_git_state = self .diffs @@ -1151,29 +1146,26 @@ impl GitStore { for (buffer_id, diff) in self.diffs.iter() { if let Some((buffer_repo, repo_path)) = self.repository_and_path_for_buffer_id(*buffer_id, cx) + && buffer_repo == repo { - if buffer_repo == repo { - diff.update(cx, |diff, cx| { - if let Some(conflict_set) = &diff.conflict_set { - let conflict_status_changed = - conflict_set.update(cx, |conflict_set, cx| { - let has_conflict = repo_snapshot.has_conflict(&repo_path); - conflict_set.set_has_conflict(has_conflict, cx) - })?; - if conflict_status_changed { - let buffer_store = self.buffer_store.read(cx); - if let Some(buffer) = buffer_store.get(*buffer_id) { - let _ = diff.reparse_conflict_markers( - buffer.read(cx).text_snapshot(), - cx, - ); - } + diff.update(cx, |diff, cx| { + if let Some(conflict_set) = &diff.conflict_set { + let conflict_status_changed = + conflict_set.update(cx, |conflict_set, cx| { + let has_conflict = repo_snapshot.has_conflict(&repo_path); + conflict_set.set_has_conflict(has_conflict, cx) + })?; + if conflict_status_changed { + let buffer_store = self.buffer_store.read(cx); + if let Some(buffer) = buffer_store.get(*buffer_id) { + let _ = diff + .reparse_conflict_markers(buffer.read(cx).text_snapshot(), cx); } } - anyhow::Ok(()) - }) - .ok(); - } + } + anyhow::Ok(()) + }) + .ok(); } } cx.emit(GitStoreEvent::RepositoryUpdated( @@ -1277,7 +1269,7 @@ impl GitStore { ) { match event { BufferStoreEvent::BufferAdded(buffer) => { - cx.subscribe(&buffer, |this, buffer, event, cx| { + cx.subscribe(buffer, |this, buffer, event, cx| { if let BufferEvent::LanguageChanged = event { let buffer_id = buffer.read(cx).remote_id(); if let Some(diff_state) = this.diffs.get(&buffer_id) { @@ -1295,7 +1287,7 @@ impl GitStore { } } BufferStoreEvent::BufferDropped(buffer_id) => { - self.diffs.remove(&buffer_id); + self.diffs.remove(buffer_id); for diffs in self.shared_diffs.values_mut() { diffs.remove(buffer_id); } @@ -1384,8 +1376,8 @@ impl GitStore { repository.update(cx, |repository, cx| { let repo_abs_path = &repository.work_directory_abs_path; if changed_repos.iter().any(|update| { - update.old_work_directory_abs_path.as_ref() == Some(&repo_abs_path) - || update.new_work_directory_abs_path.as_ref() == Some(&repo_abs_path) + update.old_work_directory_abs_path.as_ref() == Some(repo_abs_path) + || update.new_work_directory_abs_path.as_ref() == Some(repo_abs_path) }) { repository.reload_buffer_diff_bases(cx); } @@ -1536,7 +1528,7 @@ impl GitStore { }); if is_new { this._subscriptions - .push(cx.subscribe(&repo, Self::on_repository_event)) + .push(cx.subscribe(repo, Self::on_repository_event)) } repo.update(cx, { @@ -2231,13 +2223,13 @@ impl GitStore { ) -> Result<()> { let buffer_id = BufferId::new(request.payload.buffer_id)?; this.update(&mut cx, |this, cx| { - if let Some(diff_state) = this.diffs.get_mut(&buffer_id) { - if let Some(buffer) = this.buffer_store.read(cx).get(buffer_id) { - let buffer = buffer.read(cx).text_snapshot(); - diff_state.update(cx, |diff_state, cx| { - diff_state.handle_base_texts_updated(buffer, request.payload, cx); - }) - } + if let Some(diff_state) = this.diffs.get_mut(&buffer_id) + && let Some(buffer) = this.buffer_store.read(cx).get(buffer_id) + { + let buffer = buffer.read(cx).text_snapshot(); + diff_state.update(cx, |diff_state, cx| { + diff_state.handle_base_texts_updated(buffer, request.payload, cx); + }) } }) } @@ -2353,7 +2345,7 @@ impl GitStore { // All paths prefixed by a given repo will constitute a continuous range. while let Some(path) = entries.get(ix) && let Some(repo_path) = - RepositorySnapshot::abs_path_to_repo_path_inner(&repo_path, &path) + RepositorySnapshot::abs_path_to_repo_path_inner(&repo_path, path) { paths.push((repo_path, ix)); ix += 1; @@ -2507,14 +2499,14 @@ impl BufferGitState { pub fn wait_for_recalculation(&mut self) -> Option<impl Future<Output = ()> + use<>> { if *self.recalculating_tx.borrow() { let mut rx = self.recalculating_tx.subscribe(); - return Some(async move { + Some(async move { loop { let is_recalculating = rx.recv().await; if is_recalculating != Some(true) { break; } } - }); + }) } else { None } @@ -2774,6 +2766,7 @@ impl RepositorySnapshot { .iter() .map(|repo_path| repo_path.to_proto()) .collect(), + merge_message: self.merge.message.as_ref().map(|msg| msg.to_string()), project_id, id: self.id.to_proto(), abs_path: self.work_directory_abs_path.to_proto(), @@ -2836,6 +2829,7 @@ impl RepositorySnapshot { .iter() .map(|path| path.as_ref().to_proto()) .collect(), + merge_message: self.merge.message.as_ref().map(|msg| msg.to_string()), project_id, id: self.id.to_proto(), abs_path: self.work_directory_abs_path.to_proto(), @@ -2875,15 +2869,15 @@ impl RepositorySnapshot { } pub fn had_conflict_on_last_merge_head_change(&self, repo_path: &RepoPath) -> bool { - self.merge.conflicted_paths.contains(&repo_path) + self.merge.conflicted_paths.contains(repo_path) } pub fn has_conflict(&self, repo_path: &RepoPath) -> bool { let had_conflict_on_last_merge_head_change = - self.merge.conflicted_paths.contains(&repo_path); + self.merge.conflicted_paths.contains(repo_path); let has_conflict_currently = self - .status_for_path(&repo_path) - .map_or(false, |entry| entry.status.is_conflicted()); + .status_for_path(repo_path) + .is_some_and(|entry| entry.status.is_conflicted()); had_conflict_on_last_merge_head_change || has_conflict_currently } @@ -3531,14 +3525,13 @@ impl Repository { let Some(project_path) = self.repo_path_to_project_path(path, cx) else { continue; }; - if let Some(buffer) = buffer_store.get_by_path(&project_path) { - if buffer + if let Some(buffer) = buffer_store.get_by_path(&project_path) + && buffer .read(cx) .file() - .map_or(false, |file| file.disk_state().exists()) - { - save_futures.push(buffer_store.save_buffer(buffer, cx)); - } + .is_some_and(|file| file.disk_state().exists()) + { + save_futures.push(buffer_store.save_buffer(buffer, cx)); } } }) @@ -3598,14 +3591,13 @@ impl Repository { let Some(project_path) = self.repo_path_to_project_path(path, cx) else { continue; }; - if let Some(buffer) = buffer_store.get_by_path(&project_path) { - if buffer + if let Some(buffer) = buffer_store.get_by_path(&project_path) + && buffer .read(cx) .file() - .map_or(false, |file| file.disk_state().exists()) - { - save_futures.push(buffer_store.save_buffer(buffer, cx)); - } + .is_some_and(|file| file.disk_state().exists()) + { + save_futures.push(buffer_store.save_buffer(buffer, cx)); } } }) @@ -4266,6 +4258,7 @@ impl Repository { .map(proto_to_commit_details); self.snapshot.merge.conflicted_paths = conflicted_paths; + self.snapshot.merge.message = update.merge_message.map(SharedString::from); let edits = update .removed_statuses @@ -4418,14 +4411,13 @@ impl Repository { } if let Some(job) = jobs.pop_front() { - if let Some(current_key) = &job.key { - if jobs + if let Some(current_key) = &job.key + && jobs .iter() .any(|other_job| other_job.key.as_ref() == Some(current_key)) { continue; } - } (job.job)(state.clone(), cx).await; } else if let Some(job) = job_rx.next().await { jobs.push_back(job); @@ -4456,13 +4448,12 @@ impl Repository { } if let Some(job) = jobs.pop_front() { - if let Some(current_key) = &job.key { - if jobs + if let Some(current_key) = &job.key + && jobs .iter() .any(|other_job| other_job.key.as_ref() == Some(current_key)) - { - continue; - } + { + continue; } (job.job)(state.clone(), cx).await; } else if let Some(job) = job_rx.next().await { @@ -4586,10 +4577,10 @@ impl Repository { for (repo_path, status) in &*statuses.entries { changed_paths.remove(repo_path); - if cursor.seek_forward(&PathTarget::Path(repo_path), Bias::Left) { - if cursor.item().is_some_and(|entry| entry.status == *status) { - continue; - } + if cursor.seek_forward(&PathTarget::Path(repo_path), Bias::Left) + && cursor.item().is_some_and(|entry| entry.status == *status) + { + continue; } changed_path_statuses.push(Edit::Insert(StatusEntry { diff --git a/crates/project/src/git_store/git_traversal.rs b/crates/project/src/git_store/git_traversal.rs index bbcffe046d..4594e8d140 100644 --- a/crates/project/src/git_store/git_traversal.rs +++ b/crates/project/src/git_store/git_traversal.rs @@ -182,11 +182,11 @@ impl<'a> Iterator for ChildEntriesGitIter<'a> { type Item = GitEntryRef<'a>; fn next(&mut self) -> Option<Self::Item> { - if let Some(item) = self.traversal.entry() { - if item.path.starts_with(self.parent_path) { - self.traversal.advance_to_sibling(); - return Some(item); - } + if let Some(item) = self.traversal.entry() + && item.path.starts_with(self.parent_path) + { + self.traversal.advance_to_sibling(); + return Some(item); } None } @@ -211,7 +211,7 @@ impl Deref for GitEntryRef<'_> { type Target = Entry; fn deref(&self) -> &Self::Target { - &self.entry + self.entry } } diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 79f134b91a..c5a198954e 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -224,7 +224,7 @@ impl ProjectItem for ImageItem { path: &ProjectPath, cx: &mut App, ) -> Option<Task<anyhow::Result<Entity<Self>>>> { - if is_image_file(&project, &path, cx) { + if is_image_file(project, path, cx) { Some(cx.spawn({ let path = path.clone(); let project = project.clone(); @@ -446,15 +446,12 @@ impl ImageStore { event: &ImageItemEvent, cx: &mut Context<Self>, ) { - match event { - ImageItemEvent::FileHandleChanged => { - if let Some(local) = self.state.as_local() { - local.update(cx, |local, cx| { - local.image_changed_file(image, cx); - }) - } - } - _ => {} + if let ImageItemEvent::FileHandleChanged = event + && let Some(local) = self.state.as_local() + { + local.update(cx, |local, cx| { + local.image_changed_file(image, cx); + }) } } } @@ -531,13 +528,10 @@ impl ImageStoreImpl for Entity<LocalImageStore> { impl LocalImageStore { fn subscribe_to_worktree(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) { cx.subscribe(worktree, |this, worktree, event, cx| { - if worktree.read(cx).is_local() { - match event { - worktree::Event::UpdatedEntries(changes) => { - this.local_worktree_entries_changed(&worktree, changes, cx); - } - _ => {} - } + if worktree.read(cx).is_local() + && let worktree::Event::UpdatedEntries(changes) = event + { + this.local_worktree_entries_changed(&worktree, changes, cx); } }) .detach(); diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index fcfeb9c660..de6848701f 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -332,9 +332,9 @@ impl LspCommand for PrepareRename { _: Entity<LspStore>, buffer: Entity<Buffer>, _: LanguageServerId, - mut cx: AsyncApp, + cx: AsyncApp, ) -> Result<PrepareRenameResponse> { - buffer.read_with(&mut cx, |buffer, _| match message { + buffer.read_with(&cx, |buffer, _| match message { Some(lsp::PrepareRenameResponse::Range(range)) | Some(lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. }) => { let Range { start, end } = range_from_lsp(range); @@ -386,7 +386,7 @@ impl LspCommand for PrepareRename { .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -543,7 +543,7 @@ impl LspCommand for PerformRename { })? .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, new_name: message.new_name, push_to_history: false, }) @@ -658,7 +658,7 @@ impl LspCommand for GetDefinitions { })? .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -761,7 +761,7 @@ impl LspCommand for GetDeclarations { })? .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -863,7 +863,7 @@ impl LspCommand for GetImplementations { })? .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -962,7 +962,7 @@ impl LspCommand for GetTypeDefinitions { })? .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -1165,7 +1165,7 @@ pub async fn location_link_from_lsp( server_id: LanguageServerId, cx: &mut AsyncApp, ) -> Result<LocationLink> { - let (_, language_server) = language_server_for_buffer(&lsp_store, &buffer, server_id, cx)?; + let (_, language_server) = language_server_for_buffer(lsp_store, buffer, server_id, cx)?; let (origin_range, target_uri, target_range) = ( link.origin_selection_range, @@ -1330,7 +1330,7 @@ impl LspCommand for GetReferences { target_buffer_handle .clone() - .read_with(&mut cx, |target_buffer, _| { + .read_with(&cx, |target_buffer, _| { let target_start = target_buffer .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left); let target_end = target_buffer @@ -1374,7 +1374,7 @@ impl LspCommand for GetReferences { })? .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -1484,9 +1484,9 @@ impl LspCommand for GetDocumentHighlights { _: Entity<LspStore>, buffer: Entity<Buffer>, _: LanguageServerId, - mut cx: AsyncApp, + cx: AsyncApp, ) -> Result<Vec<DocumentHighlight>> { - buffer.read_with(&mut cx, |buffer, _| { + buffer.read_with(&cx, |buffer, _| { let mut lsp_highlights = lsp_highlights.unwrap_or_default(); lsp_highlights.sort_unstable_by_key(|h| (h.range.start, Reverse(h.range.end))); lsp_highlights @@ -1534,7 +1534,7 @@ impl LspCommand for GetDocumentHighlights { })? .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -1865,7 +1865,7 @@ impl LspCommand for GetSignatureHelp { })? .await .with_context(|| format!("waiting for version for buffer {}", buffer.entity_id()))?; - let buffer_snapshot = buffer.read_with(&mut cx, |buffer, _| buffer.snapshot())?; + let buffer_snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?; Ok(Self { position: payload .position @@ -1947,13 +1947,13 @@ impl LspCommand for GetHover { _: Entity<LspStore>, buffer: Entity<Buffer>, _: LanguageServerId, - mut cx: AsyncApp, + cx: AsyncApp, ) -> Result<Self::Response> { let Some(hover) = message else { return Ok(None); }; - let (language, range) = buffer.read_with(&mut cx, |buffer, _| { + let (language, range) = buffer.read_with(&cx, |buffer, _| { ( buffer.language().cloned(), hover.range.map(|range| { @@ -2039,7 +2039,7 @@ impl LspCommand for GetHover { })? .await?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -2113,7 +2113,7 @@ impl LspCommand for GetHover { return Ok(None); } - let language = buffer.read_with(&mut cx, |buffer, _| buffer.language().cloned())?; + let language = buffer.read_with(&cx, |buffer, _| buffer.language().cloned())?; let range = if let (Some(start), Some(end)) = (message.start, message.end) { language::proto::deserialize_anchor(start) .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end)) @@ -2208,7 +2208,7 @@ impl LspCommand for GetCompletions { let unfiltered_completions_count = completions.len(); let language_server_adapter = lsp_store - .read_with(&mut cx, |lsp_store, _| { + .read_with(&cx, |lsp_store, _| { lsp_store.language_server_adapter_for_id(server_id) })? .with_context(|| format!("no language server with id {server_id}"))?; @@ -2341,15 +2341,14 @@ impl LspCommand for GetCompletions { .zip(completion_edits) .map(|(mut lsp_completion, mut edit)| { LineEnding::normalize(&mut edit.new_text); - if lsp_completion.data.is_none() { - if let Some(default_data) = lsp_defaults + if lsp_completion.data.is_none() + && let Some(default_data) = lsp_defaults .as_ref() .and_then(|item_defaults| item_defaults.data.clone()) - { - // Servers (e.g. JDTLS) prefer unchanged completions, when resolving the items later, - // so we do not insert the defaults here, but `data` is needed for resolving, so this is an exception. - lsp_completion.data = Some(default_data); - } + { + // Servers (e.g. JDTLS) prefer unchanged completions, when resolving the items later, + // so we do not insert the defaults here, but `data` is needed for resolving, so this is an exception. + lsp_completion.data = Some(default_data); } CoreCompletion { replace_range: edit.replace_range, @@ -2395,7 +2394,7 @@ impl LspCommand for GetCompletions { .position .and_then(language::proto::deserialize_anchor) .map(|p| { - buffer.read_with(&mut cx, |buffer, _| { + buffer.read_with(&cx, |buffer, _| { buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left) }) }) @@ -2502,8 +2501,8 @@ pub(crate) fn parse_completion_text_edit( }; Some(ParsedCompletionEdit { - insert_range: insert_range, - replace_range: replace_range, + insert_range, + replace_range, new_text: new_text.clone(), }) } @@ -2623,10 +2622,10 @@ impl LspCommand for GetCodeActions { .filter_map(|entry| { let (lsp_action, resolved) = match entry { lsp::CodeActionOrCommand::CodeAction(lsp_action) => { - if let Some(command) = lsp_action.command.as_ref() { - if !available_commands.contains(&command.command) { - return None; - } + if let Some(command) = lsp_action.command.as_ref() + && !available_commands.contains(&command.command) + { + return None; } (LspAction::Action(Box::new(lsp_action)), false) } @@ -2641,10 +2640,9 @@ impl LspCommand for GetCodeActions { if let Some((requested_kinds, kind)) = requested_kinds_set.as_ref().zip(lsp_action.action_kind()) + && !requested_kinds.contains(&kind) { - if !requested_kinds.contains(&kind) { - return None; - } + return None; } Some(CodeAction { @@ -2864,7 +2862,7 @@ impl LspCommand for OnTypeFormatting { })?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, trigger: message.trigger.clone(), options, push_to_history: false, @@ -3449,9 +3447,7 @@ impl LspCommand for GetCodeLens { .server_capabilities .code_lens_provider .as_ref() - .map_or(false, |code_lens_options| { - code_lens_options.resolve_provider.unwrap_or(false) - }) + .is_some_and(|code_lens_options| code_lens_options.resolve_provider.unwrap_or(false)) } fn to_lsp( @@ -3476,9 +3472,9 @@ impl LspCommand for GetCodeLens { lsp_store: Entity<LspStore>, buffer: Entity<Buffer>, server_id: LanguageServerId, - mut cx: AsyncApp, + cx: AsyncApp, ) -> anyhow::Result<Vec<CodeAction>> { - let snapshot = buffer.read_with(&mut cx, |buffer, _| buffer.snapshot())?; + let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?; let language_server = cx.update(|cx| { lsp_store .read(cx) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 802b304e94..12505a6a03 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -74,8 +74,8 @@ use lsp::{ FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, LanguageServerName, LanguageServerSelector, LspRequestFuture, MessageActionItem, MessageType, - OneOf, RenameFilesParams, SymbolKind, TextEdit, WillRenameFiles, WorkDoneProgressCancelParams, - WorkspaceFolder, notification::DidRenameFiles, + OneOf, RenameFilesParams, SymbolKind, TextDocumentSyncSaveOptions, TextEdit, WillRenameFiles, + WorkDoneProgressCancelParams, WorkspaceFolder, notification::DidRenameFiles, }; use node_runtime::read_package_installed_version; use parking_lot::Mutex; @@ -296,7 +296,7 @@ impl LocalLspStore { let stderr_capture = Arc::new(Mutex::new(Some(String::new()))); let server_id = self.languages.next_language_server_id(); - log::info!( + log::trace!( "attempting to start language server {:?}, path: {root_path:?}, id: {server_id}", adapter.name.0 ); @@ -442,14 +442,14 @@ impl LocalLspStore { match result { Ok(server) => { lsp_store - .update(cx, |lsp_store, mut cx| { + .update(cx, |lsp_store, cx| { lsp_store.insert_newly_running_language_server( adapter, server.clone(), server_id, key, pending_workspace_folders, - &mut cx, + cx, ); }) .ok(); @@ -550,7 +550,7 @@ impl LocalLspStore { if let Some(settings) = settings.binary.as_ref() { if let Some(arguments) = &settings.arguments { - binary.arguments = arguments.into_iter().map(Into::into).collect(); + binary.arguments = arguments.iter().map(Into::into).collect(); } if let Some(env) = &settings.env { shell_env.extend(env.iter().map(|(k, v)| (k.clone(), v.clone()))); @@ -669,10 +669,10 @@ impl LocalLspStore { let this = this.clone(); move |_, cx| { let this = this.clone(); - let mut cx = cx.clone(); + let cx = cx.clone(); async move { - let Some(server) = this - .read_with(&mut cx, |this, _| this.language_server_for_id(server_id))? + let Some(server) = + this.read_with(&cx, |this, _| this.language_server_for_id(server_id))? else { return Ok(None); }; @@ -701,10 +701,9 @@ impl LocalLspStore { async move { this.update(&mut cx, |this, _| { if let Some(status) = this.language_server_statuses.get_mut(&server_id) + && let lsp::NumberOrString::String(token) = params.token { - if let lsp::NumberOrString::String(token) = params.token { - status.progress_tokens.insert(token); - } + status.progress_tokens.insert(token); } })?; @@ -1015,10 +1014,10 @@ impl LocalLspStore { } } LanguageServerState::Starting { startup, .. } => { - if let Some(server) = startup.await { - if let Some(shutdown) = server.shutdown() { - shutdown.await; - } + if let Some(server) = startup.await + && let Some(shutdown) = server.shutdown() + { + shutdown.await; } } } @@ -1039,7 +1038,7 @@ impl LocalLspStore { if let Some(LanguageServerState::Running { server, .. }) = self.language_servers.get(&state.id) { - return Some(server); + Some(server) } else { None } @@ -1061,8 +1060,8 @@ impl LocalLspStore { }; let delegate: Arc<dyn ManifestDelegate> = Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot())); - let root = self - .lsp_tree + + self.lsp_tree .get( project_path, language.name(), @@ -1070,9 +1069,7 @@ impl LocalLspStore { &delegate, cx, ) - .collect::<Vec<_>>(); - - root + .collect::<Vec<_>>() } fn language_server_ids_for_buffer( @@ -1880,7 +1877,7 @@ impl LocalLspStore { ) -> Result<Vec<(Range<Anchor>, Arc<str>)>> { let capabilities = &language_server.capabilities(); let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); - if range_formatting_provider.map_or(false, |provider| provider == &OneOf::Left(false)) { + if range_formatting_provider == Some(&OneOf::Left(false)) { anyhow::bail!( "{} language server does not support range formatting", language_server.name() @@ -1927,7 +1924,7 @@ impl LocalLspStore { if let Some(lsp_edits) = lsp_edits { this.update(cx, |this, cx| { this.as_local_mut().unwrap().edits_from_lsp( - &buffer_handle, + buffer_handle, lsp_edits, language_server.server_id(), None, @@ -2384,21 +2381,22 @@ impl LocalLspStore { return None; } if !only_register_servers.is_empty() { - if let Some(server_id) = server_node.server_id() { - if !only_register_servers.contains(&LanguageServerSelector::Id(server_id)) { - return None; - } + if let Some(server_id) = server_node.server_id() + && !only_register_servers.contains(&LanguageServerSelector::Id(server_id)) + { + return None; } - if let Some(name) = server_node.name() { - if !only_register_servers.contains(&LanguageServerSelector::Name(name)) { - return None; - } + if let Some(name) = server_node.name() + && !only_register_servers.contains(&LanguageServerSelector::Name(name)) + { + return None; } } let server_id = server_node.server_id_or_init(|disposition| { let path = &disposition.path; - let server_id = { + + { let uri = Url::from_file_path(worktree.read(cx).abs_path().join(&path.path)); @@ -2410,15 +2408,13 @@ impl LocalLspStore { cx, ); - if let Some(state) = self.language_servers.get(&server_id) { - if let Ok(uri) = uri { - state.add_workspace_folder(uri); - }; - } + if let Some(state) = self.language_servers.get(&server_id) + && let Ok(uri) = uri + { + state.add_workspace_folder(uri); + }; server_id - }; - - server_id + } })?; let server_state = self.language_servers.get(&server_id)?; if let LanguageServerState::Running { @@ -2643,7 +2639,7 @@ impl LocalLspStore { this.request_lsp(buffer.clone(), server, request, cx) })? .await?; - return Ok(actions); + Ok(actions) } pub async fn execute_code_actions_on_server( @@ -2719,7 +2715,7 @@ impl LocalLspStore { } } } - return Ok(()); + Ok(()) } pub async fn deserialize_text_edits( @@ -2958,11 +2954,11 @@ impl LocalLspStore { .update(cx, |this, cx| { let path = buffer_to_edit.read(cx).project_path(cx); let active_entry = this.active_entry; - let is_active_entry = path.clone().map_or(false, |project_path| { + let is_active_entry = path.clone().is_some_and(|project_path| { this.worktree_store .read(cx) .entry_for_path(&project_path, cx) - .map_or(false, |entry| Some(entry.id) == active_entry) + .is_some_and(|entry| Some(entry.id) == active_entry) }); let local = this.as_local_mut().unwrap(); @@ -3048,16 +3044,14 @@ impl LocalLspStore { buffer.edit([(range, text)], None, cx); } - let transaction = buffer.end_transaction(cx).and_then(|transaction_id| { + buffer.end_transaction(cx).and_then(|transaction_id| { if push_to_history { buffer.finalize_last_transaction(); buffer.get_transaction(transaction_id).cloned() } else { buffer.forget_transaction(transaction_id) } - }); - - transaction + }) })?; if let Some(transaction) = transaction { project_transaction.0.insert(buffer_to_edit, transaction); @@ -3115,7 +3109,7 @@ impl LocalLspStore { let mut servers_to_remove = BTreeSet::default(); let mut servers_to_preserve = HashSet::default(); - for (seed, ref state) in &self.language_server_ids { + for (seed, state) in &self.language_server_ids { if seed.worktree_id == id_to_remove { servers_to_remove.insert(state.id); } else { @@ -3169,7 +3163,7 @@ impl LocalLspStore { for watcher in watchers { if let Some((worktree, literal_prefix, pattern)) = - self.worktree_and_path_for_file_watcher(&worktrees, &watcher, cx) + self.worktree_and_path_for_file_watcher(&worktrees, watcher, cx) { worktree.update(cx, |worktree, _| { if let Some((tree, glob)) = @@ -3844,13 +3838,13 @@ impl LspStore { } BufferStoreEvent::BufferChangedFilePath { buffer, old_file } => { let buffer_id = buffer.read(cx).remote_id(); - if let Some(local) = self.as_local_mut() { - if let Some(old_file) = File::from_dyn(old_file.as_ref()) { - local.reset_buffer(buffer, old_file, cx); + if let Some(local) = self.as_local_mut() + && let Some(old_file) = File::from_dyn(old_file.as_ref()) + { + local.reset_buffer(buffer, old_file, cx); - if local.registered_buffers.contains_key(&buffer_id) { - local.unregister_old_buffer_from_language_servers(buffer, old_file, cx); - } + if local.registered_buffers.contains_key(&buffer_id) { + local.unregister_old_buffer_from_language_servers(buffer, old_file, cx); } } @@ -4039,7 +4033,7 @@ impl LspStore { servers.push((json_adapter, json_server, json_delegate)); } - return Some(servers); + Some(servers) }) .ok() .flatten(); @@ -4051,7 +4045,7 @@ impl LspStore { let Ok(Some((fs, _))) = this.read_with(cx, |this, _| { let local = this.as_local()?; let toolchain_store = local.toolchain_store().clone(); - return Some((local.fs.clone(), toolchain_store)); + Some((local.fs.clone(), toolchain_store)) }) else { return; }; @@ -4131,7 +4125,7 @@ impl LspStore { local.registered_buffers.remove(&buffer_id); local.buffers_opened_in_servers.remove(&buffer_id); if let Some(file) = File::from_dyn(buffer.read(cx).file()).cloned() { - local.unregister_old_buffer_from_language_servers(&buffer, &file, cx); + local.unregister_old_buffer_from_language_servers(buffer, &file, cx); } } }) @@ -4201,14 +4195,12 @@ impl LspStore { if local .registered_buffers .contains_key(&buffer.read(cx).remote_id()) - { - if let Some(file_url) = + && let Some(file_url) = file_path_to_lsp_url(&f.abs_path(cx)).log_err() - { - local.unregister_buffer_from_language_servers( - &buffer, &file_url, cx, - ); - } + { + local.unregister_buffer_from_language_servers( + &buffer, &file_url, cx, + ); } } } @@ -4306,25 +4298,19 @@ impl LspStore { let buffer = buffer_entity.read(cx); let buffer_file = buffer.file().cloned(); let buffer_id = buffer.remote_id(); - if let Some(local_store) = self.as_local_mut() { - if local_store.registered_buffers.contains_key(&buffer_id) { - if let Some(abs_path) = - File::from_dyn(buffer_file.as_ref()).map(|file| file.abs_path(cx)) - { - if let Some(file_url) = file_path_to_lsp_url(&abs_path).log_err() { - local_store.unregister_buffer_from_language_servers( - buffer_entity, - &file_url, - cx, - ); - } - } - } + if let Some(local_store) = self.as_local_mut() + && local_store.registered_buffers.contains_key(&buffer_id) + && let Some(abs_path) = + File::from_dyn(buffer_file.as_ref()).map(|file| file.abs_path(cx)) + && let Some(file_url) = file_path_to_lsp_url(&abs_path).log_err() + { + local_store.unregister_buffer_from_language_servers(buffer_entity, &file_url, cx); } buffer_entity.update(cx, |buffer, cx| { - if buffer.language().map_or(true, |old_language| { - !Arc::ptr_eq(old_language, &new_language) - }) { + if buffer + .language() + .is_none_or(|old_language| !Arc::ptr_eq(old_language, &new_language)) + { buffer.set_language(Some(new_language.clone()), cx); } }); @@ -4336,33 +4322,28 @@ impl LspStore { let worktree_id = if let Some(file) = buffer_file { let worktree = file.worktree.clone(); - if let Some(local) = self.as_local_mut() { - if local.registered_buffers.contains_key(&buffer_id) { - local.register_buffer_with_language_servers( - buffer_entity, - HashSet::default(), - cx, - ); - } + if let Some(local) = self.as_local_mut() + && local.registered_buffers.contains_key(&buffer_id) + { + local.register_buffer_with_language_servers(buffer_entity, HashSet::default(), cx); } Some(worktree.read(cx).id()) } else { None }; - if settings.prettier.allowed { - if let Some(prettier_plugins) = prettier_store::prettier_plugins_for_language(&settings) - { - let prettier_store = self.as_local().map(|s| s.prettier_store.clone()); - if let Some(prettier_store) = prettier_store { - prettier_store.update(cx, |prettier_store, cx| { - prettier_store.install_default_prettier( - worktree_id, - prettier_plugins.iter().map(|s| Arc::from(s.as_str())), - cx, - ) - }) - } + if settings.prettier.allowed + && let Some(prettier_plugins) = prettier_store::prettier_plugins_for_language(&settings) + { + let prettier_store = self.as_local().map(|s| s.prettier_store.clone()); + if let Some(prettier_store) = prettier_store { + prettier_store.update(cx, |prettier_store, cx| { + prettier_store.install_default_prettier( + worktree_id, + prettier_plugins.iter().map(|s| Arc::from(s.as_str())), + cx, + ) + }) } } @@ -4381,26 +4362,23 @@ impl LspStore { } pub(crate) fn send_diagnostic_summaries(&self, worktree: &mut Worktree) { - if let Some((client, downstream_project_id)) = self.downstream_client.clone() { - if let Some(diangostic_summaries) = self.diagnostic_summaries.get(&worktree.id()) { - let mut summaries = - diangostic_summaries - .into_iter() - .flat_map(|(path, summaries)| { - summaries - .into_iter() - .map(|(server_id, summary)| summary.to_proto(*server_id, path)) - }); - if let Some(summary) = summaries.next() { - client - .send(proto::UpdateDiagnosticSummary { - project_id: downstream_project_id, - worktree_id: worktree.id().to_proto(), - summary: Some(summary), - more_summaries: summaries.collect(), - }) - .log_err(); - } + if let Some((client, downstream_project_id)) = self.downstream_client.clone() + && let Some(diangostic_summaries) = self.diagnostic_summaries.get(&worktree.id()) + { + let mut summaries = diangostic_summaries.iter().flat_map(|(path, summaries)| { + summaries + .iter() + .map(|(server_id, summary)| summary.to_proto(*server_id, path)) + }); + if let Some(summary) = summaries.next() { + client + .send(proto::UpdateDiagnosticSummary { + project_id: downstream_project_id, + worktree_id: worktree.id().to_proto(), + summary: Some(summary), + more_summaries: summaries.collect(), + }) + .log_err(); } } } @@ -4453,7 +4431,7 @@ impl LspStore { .contains(&server_status.name) .then_some(server_id) }) - .filter_map(|server_id| self.lsp_server_capabilities.get(&server_id)) + .filter_map(|server_id| self.lsp_server_capabilities.get(server_id)) .any(check) } @@ -4530,7 +4508,7 @@ impl LspStore { if !request.check_capabilities(language_server.adapter_server_capabilities()) { return Task::ready(Ok(Default::default())); } - return cx.spawn(async move |this, cx| { + cx.spawn(async move |this, cx| { let lsp_request = language_server.request::<R::LspRequest>(lsp_params); let id = lsp_request.id(); @@ -4579,7 +4557,7 @@ impl LspStore { anyhow::anyhow!(message) })?; - let response = request + request .response_from_lsp( response, this.upgrade().context("no app context")?, @@ -4587,9 +4565,8 @@ impl LspStore { language_server.server_id(), cx.clone(), ) - .await; - response - }); + .await + }) } fn on_settings_changed(&mut self, cx: &mut Context<Self>) { @@ -4730,11 +4707,11 @@ impl LspStore { &language.name(), cx, ); - if let Some(state) = local.language_servers.get(&server_id) { - if let Ok(uri) = uri { - state.add_workspace_folder(uri); - }; - } + if let Some(state) = local.language_servers.get(&server_id) + && let Ok(uri) = uri + { + state.add_workspace_folder(uri); + }; server_id }); @@ -4805,8 +4782,8 @@ impl LspStore { LocalLspStore::try_resolve_code_action(&lang_server, &mut action) .await .context("resolving a code action")?; - if let Some(edit) = action.lsp_action.edit() { - if edit.changes.is_some() || edit.document_changes.is_some() { + if let Some(edit) = action.lsp_action.edit() + && (edit.changes.is_some() || edit.document_changes.is_some()) { return LocalLspStore::deserialize_workspace_edit( this.upgrade().context("no app present")?, edit.clone(), @@ -4817,7 +4794,6 @@ impl LspStore { ) .await; } - } if let Some(command) = action.lsp_action.command() { let server_capabilities = lang_server.capabilities(); @@ -4869,7 +4845,7 @@ impl LspStore { push_to_history: bool, cx: &mut Context<Self>, ) -> Task<anyhow::Result<ProjectTransaction>> { - if let Some(_) = self.as_local() { + if self.as_local().is_some() { cx.spawn(async move |lsp_store, cx| { let buffers = buffers.into_iter().collect::<Vec<_>>(); let result = LocalLspStore::execute_code_action_kind_locally( @@ -5419,7 +5395,7 @@ impl LspStore { ) -> Task<Result<Vec<LocationLink>>> { if let Some((upstream_client, project_id)) = self.upstream_client() { let request = GetTypeDefinitions { position }; - if !self.is_capable_for_proto_request(&buffer, &request, cx) { + if !self.is_capable_for_proto_request(buffer, &request, cx) { return Task::ready(Ok(Vec::new())); } let request_task = upstream_client.request(proto::MultiLspQuery { @@ -5573,7 +5549,7 @@ impl LspStore { ) -> Task<Result<Vec<Location>>> { if let Some((upstream_client, project_id)) = self.upstream_client() { let request = GetReferences { position }; - if !self.is_capable_for_proto_request(&buffer, &request, cx) { + if !self.is_capable_for_proto_request(buffer, &request, cx) { return Task::ready(Ok(Vec::new())); } let request_task = upstream_client.request(proto::MultiLspQuery { @@ -5736,28 +5712,28 @@ impl LspStore { let version_queried_for = buffer.read(cx).version(); let buffer_id = buffer.read(cx).remote_id(); - if let Some(cached_data) = self.lsp_code_lens.get(&buffer_id) { - if !version_queried_for.changed_since(&cached_data.lens_for_version) { - let has_different_servers = self.as_local().is_some_and(|local| { - local - .buffers_opened_in_servers - .get(&buffer_id) - .cloned() - .unwrap_or_default() - != cached_data.lens.keys().copied().collect() - }); - if !has_different_servers { - return Task::ready(Ok(cached_data.lens.values().flatten().cloned().collect())) - .shared(); - } + if let Some(cached_data) = self.lsp_code_lens.get(&buffer_id) + && !version_queried_for.changed_since(&cached_data.lens_for_version) + { + let has_different_servers = self.as_local().is_some_and(|local| { + local + .buffers_opened_in_servers + .get(&buffer_id) + .cloned() + .unwrap_or_default() + != cached_data.lens.keys().copied().collect() + }); + if !has_different_servers { + return Task::ready(Ok(cached_data.lens.values().flatten().cloned().collect())) + .shared(); } } let lsp_data = self.lsp_code_lens.entry(buffer_id).or_default(); - if let Some((updating_for, running_update)) = &lsp_data.update { - if !version_queried_for.changed_since(&updating_for) { - return running_update.clone(); - } + if let Some((updating_for, running_update)) = &lsp_data.update + && !version_queried_for.changed_since(updating_for) + { + return running_update.clone(); } let buffer = buffer.clone(); let query_version_queried_for = version_queried_for.clone(); @@ -6372,11 +6348,11 @@ impl LspStore { .old_replace_start .and_then(deserialize_anchor) .zip(response.old_replace_end.and_then(deserialize_anchor)); - if let Some((old_replace_start, old_replace_end)) = replace_range { - if !response.new_text.is_empty() { - completion.new_text = response.new_text; - completion.replace_range = old_replace_start..old_replace_end; - } + if let Some((old_replace_start, old_replace_end)) = replace_range + && !response.new_text.is_empty() + { + completion.new_text = response.new_text; + completion.replace_range = old_replace_start..old_replace_end; } Ok(()) @@ -6751,33 +6727,33 @@ impl LspStore { LspFetchStrategy::UseCache { known_cache_version, } => { - if let Some(cached_data) = self.lsp_document_colors.get(&buffer_id) { - if !version_queried_for.changed_since(&cached_data.colors_for_version) { - let has_different_servers = self.as_local().is_some_and(|local| { - local - .buffers_opened_in_servers - .get(&buffer_id) - .cloned() - .unwrap_or_default() - != cached_data.colors.keys().copied().collect() - }); - if !has_different_servers { - if Some(cached_data.cache_version) == known_cache_version { - return None; - } else { - return Some( - Task::ready(Ok(DocumentColors { - colors: cached_data - .colors - .values() - .flatten() - .cloned() - .collect(), - cache_version: Some(cached_data.cache_version), - })) - .shared(), - ); - } + if let Some(cached_data) = self.lsp_document_colors.get(&buffer_id) + && !version_queried_for.changed_since(&cached_data.colors_for_version) + { + let has_different_servers = self.as_local().is_some_and(|local| { + local + .buffers_opened_in_servers + .get(&buffer_id) + .cloned() + .unwrap_or_default() + != cached_data.colors.keys().copied().collect() + }); + if !has_different_servers { + if Some(cached_data.cache_version) == known_cache_version { + return None; + } else { + return Some( + Task::ready(Ok(DocumentColors { + colors: cached_data + .colors + .values() + .flatten() + .cloned() + .collect(), + cache_version: Some(cached_data.cache_version), + })) + .shared(), + ); } } } @@ -6785,10 +6761,10 @@ impl LspStore { } let lsp_data = self.lsp_document_colors.entry(buffer_id).or_default(); - if let Some((updating_for, running_update)) = &lsp_data.colors_update { - if !version_queried_for.changed_since(&updating_for) { - return Some(running_update.clone()); - } + if let Some((updating_for, running_update)) = &lsp_data.colors_update + && !version_queried_for.changed_since(updating_for) + { + return Some(running_update.clone()); } let query_version_queried_for = version_queried_for.clone(); let new_task = cx @@ -7314,7 +7290,7 @@ impl LspStore { include_ignored || worktree .entry_for_path(path.as_ref()) - .map_or(false, |entry| !entry.is_ignored) + .is_some_and(|entry| !entry.is_ignored) }) .flat_map(move |(path, summaries)| { summaries.iter().map(move |(server_id, summary)| { @@ -7553,7 +7529,7 @@ impl LspStore { .ok() .flatten()?; - log::info!("Refreshing workspace configurations for servers {refreshed_servers:?}"); + log::debug!("Refreshing workspace configurations for servers {refreshed_servers:?}"); // TODO this asynchronous job runs concurrently with extension (de)registration and may take enough time for a certain extension // to stop and unregister its language server wrapper. // This is racy : an extension might have already removed all `local.language_servers` state, but here we `.clone()` and hold onto it anyway. @@ -7820,7 +7796,7 @@ impl LspStore { } None => { diagnostics_summary = Some(proto::UpdateDiagnosticSummary { - project_id: project_id, + project_id, worktree_id: worktree_id.to_proto(), summary: Some(proto::DiagnosticSummary { path: project_path.path.as_ref().to_proto(), @@ -8171,7 +8147,7 @@ impl LspStore { envelope: TypedEnvelope<proto::MultiLspQuery>, mut cx: AsyncApp, ) -> Result<proto::MultiLspQueryResponse> { - let response_from_ssh = lsp_store.read_with(&mut cx, |this, _| { + let response_from_ssh = lsp_store.read_with(&cx, |this, _| { let (upstream_client, project_id) = this.upstream_client()?; let mut payload = envelope.payload.clone(); payload.project_id = project_id; @@ -8193,7 +8169,7 @@ impl LspStore { buffer.wait_for_version(version.clone()) })? .await?; - let buffer_version = buffer.read_with(&mut cx, |buffer, _| buffer.version())?; + let buffer_version = buffer.read_with(&cx, |buffer, _| buffer.version())?; match envelope .payload .strategy @@ -8734,7 +8710,7 @@ impl LspStore { })? .context("worktree not found")?; let (old_abs_path, new_abs_path) = { - let root_path = worktree.read_with(&mut cx, |this, _| this.abs_path())?; + let root_path = worktree.read_with(&cx, |this, _| this.abs_path())?; let new_path = PathBuf::from_proto(envelope.payload.new_path.clone()); (root_path.join(&old_path), root_path.join(&new_path)) }; @@ -8749,7 +8725,7 @@ impl LspStore { ) .await; let response = Worktree::handle_rename_entry(worktree, envelope.payload, cx.clone()).await; - this.read_with(&mut cx, |this, _| { + this.read_with(&cx, |this, _| { this.did_rename_entry(worktree_id, &old_abs_path, &new_abs_path, is_dir); }) .ok(); @@ -8785,12 +8761,11 @@ impl LspStore { if summary.is_empty() { if let Some(worktree_summaries) = lsp_store.diagnostic_summaries.get_mut(&worktree_id) + && let Some(summaries) = worktree_summaries.get_mut(&path) { - if let Some(summaries) = worktree_summaries.get_mut(&path) { - summaries.remove(&server_id); - if summaries.is_empty() { - worktree_summaries.remove(&path); - } + summaries.remove(&server_id); + if summaries.is_empty() { + worktree_summaries.remove(&path); } } } else { @@ -8984,10 +8959,10 @@ impl LspStore { async fn handle_lsp_ext_cancel_flycheck( lsp_store: Entity<Self>, envelope: TypedEnvelope<proto::LspExtCancelFlycheck>, - mut cx: AsyncApp, + cx: AsyncApp, ) -> Result<proto::Ack> { let server_id = LanguageServerId(envelope.payload.language_server_id as usize); - lsp_store.read_with(&mut cx, |lsp_store, _| { + lsp_store.read_with(&cx, |lsp_store, _| { if let Some(server) = lsp_store.language_server_for_id(server_id) { server .notify::<lsp_store::lsp_ext_command::LspExtCancelFlycheck>(&()) @@ -9036,10 +9011,10 @@ impl LspStore { async fn handle_lsp_ext_clear_flycheck( lsp_store: Entity<Self>, envelope: TypedEnvelope<proto::LspExtClearFlycheck>, - mut cx: AsyncApp, + cx: AsyncApp, ) -> Result<proto::Ack> { let server_id = LanguageServerId(envelope.payload.language_server_id as usize); - lsp_store.read_with(&mut cx, |lsp_store, _| { + lsp_store.read_with(&cx, |lsp_store, _| { if let Some(server) = lsp_store.language_server_for_id(server_id) { server .notify::<lsp_store::lsp_ext_command::LspExtClearFlycheck>(&()) @@ -9359,9 +9334,7 @@ impl LspStore { let is_disk_based_diagnostics_progress = disk_based_diagnostics_progress_token .as_ref() - .map_or(false, |disk_based_token| { - token.starts_with(disk_based_token) - }); + .is_some_and(|disk_based_token| token.starts_with(disk_based_token)); match progress { lsp::WorkDoneProgress::Begin(report) => { @@ -9491,10 +9464,10 @@ impl LspStore { cx: &mut Context<Self>, ) { if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { - if let Some(work) = status.pending_work.remove(&token) { - if !work.is_disk_based_diagnostics_progress { - cx.emit(LspStoreEvent::RefreshInlayHints); - } + if let Some(work) = status.pending_work.remove(&token) + && !work.is_disk_based_diagnostics_progress + { + cx.emit(LspStoreEvent::RefreshInlayHints); } cx.notify(); } @@ -9807,7 +9780,7 @@ impl LspStore { let peer_id = envelope.original_sender_id().unwrap_or_default(); let symbol = envelope.payload.symbol.context("invalid symbol")?; let symbol = Self::deserialize_symbol(symbol)?; - let symbol = this.read_with(&mut cx, |this, _| { + let symbol = this.read_with(&cx, |this, _| { let signature = this.symbol_signature(&symbol.path); anyhow::ensure!(signature == symbol.signature, "invalid symbol signature"); Ok(symbol) @@ -10057,7 +10030,7 @@ impl LspStore { ) -> Shared<Task<Option<HashMap<String, String>>>> { if let Some(environment) = &self.as_local().map(|local| local.environment.clone()) { environment.update(cx, |env, cx| { - env.get_buffer_environment(&buffer, &self.worktree_store, cx) + env.get_buffer_environment(buffer, &self.worktree_store, cx) }) } else { Task::ready(None).shared() @@ -10073,7 +10046,7 @@ impl LspStore { cx: &mut Context<Self>, ) -> Task<anyhow::Result<ProjectTransaction>> { let logger = zlog::scoped!("format"); - if let Some(_) = self.as_local() { + if self.as_local().is_some() { zlog::trace!(logger => "Formatting locally"); let logger = zlog::scoped!(logger => "local"); let buffers = buffers @@ -10288,10 +10261,10 @@ impl LspStore { None => None, }; - if let Some(server) = server { - if let Some(shutdown) = server.shutdown() { - shutdown.await; - } + if let Some(server) = server + && let Some(shutdown) = server.shutdown() + { + shutdown.await; } } @@ -10565,18 +10538,18 @@ impl LspStore { for buffer in buffers { buffer.update(cx, |buffer, cx| { language_servers_to_stop.extend(local.language_server_ids_for_buffer(buffer, cx)); - if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) { - if covered_worktrees.insert(worktree_id) { - language_server_names_to_stop.retain(|name| { - let old_ids_count = language_servers_to_stop.len(); - let all_language_servers_with_this_name = local - .language_server_ids - .iter() - .filter_map(|(seed, state)| seed.name.eq(name).then(|| state.id)); - language_servers_to_stop.extend(all_language_servers_with_this_name); - old_ids_count == language_servers_to_stop.len() - }); - } + if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) + && covered_worktrees.insert(worktree_id) + { + language_server_names_to_stop.retain(|name| { + let old_ids_count = language_servers_to_stop.len(); + let all_language_servers_with_this_name = local + .language_server_ids + .iter() + .filter_map(|(seed, state)| seed.name.eq(name).then(|| state.id)); + language_servers_to_stop.extend(all_language_servers_with_this_name); + old_ids_count == language_servers_to_stop.len() + }); } }); } @@ -10694,7 +10667,7 @@ impl LspStore { let is_supporting = diagnostic .related_information .as_ref() - .map_or(false, |infos| { + .is_some_and(|infos| { infos.iter().any(|info| { primary_diagnostic_group_ids.contains_key(&( source, @@ -10707,11 +10680,11 @@ impl LspStore { let is_unnecessary = diagnostic .tags .as_ref() - .map_or(false, |tags| tags.contains(&DiagnosticTag::UNNECESSARY)); + .is_some_and(|tags| tags.contains(&DiagnosticTag::UNNECESSARY)); let underline = self .language_server_adapter_for_id(server_id) - .map_or(true, |adapter| adapter.underline_diagnostic(diagnostic)); + .is_none_or(|adapter| adapter.underline_diagnostic(diagnostic)); if is_supporting { supporting_diagnostics.insert( @@ -10721,7 +10694,7 @@ impl LspStore { } else { let group_id = post_inc(&mut self.as_local_mut().unwrap().next_diagnostic_group_id); let is_disk_based = - source.map_or(false, |source| disk_based_sources.contains(source)); + source.is_some_and(|source| disk_based_sources.contains(source)); sources_by_group_id.insert(group_id, source); primary_diagnostic_group_ids @@ -11081,10 +11054,10 @@ impl LspStore { if let Some((LanguageServerState::Running { server, .. }, status)) = server.zip(status) { for (token, progress) in &status.pending_work { - if let Some(token_to_cancel) = token_to_cancel.as_ref() { - if token != token_to_cancel { - continue; - } + if let Some(token_to_cancel) = token_to_cancel.as_ref() + && token != token_to_cancel + { + continue; } if progress.is_cancellable { server @@ -11175,7 +11148,7 @@ impl LspStore { let Some(local) = self.as_local() else { return }; local.prettier_store.update(cx, |prettier_store, cx| { - prettier_store.update_prettier_settings(&worktree_handle, changes, cx) + prettier_store.update_prettier_settings(worktree_handle, changes, cx) }); let worktree_id = worktree_handle.read(cx).id(); @@ -11191,38 +11164,36 @@ impl LspStore { for server_id in &language_server_ids { if let Some(LanguageServerState::Running { server, .. }) = local.language_servers.get(server_id) - { - if let Some(watched_paths) = local + && let Some(watched_paths) = local .language_server_watched_paths .get(server_id) .and_then(|paths| paths.worktree_paths.get(&worktree_id)) - { - let params = lsp::DidChangeWatchedFilesParams { - changes: changes - .iter() - .filter_map(|(path, _, change)| { - if !watched_paths.is_match(path) { - return None; - } - let typ = match change { - PathChange::Loaded => return None, - PathChange::Added => lsp::FileChangeType::CREATED, - PathChange::Removed => lsp::FileChangeType::DELETED, - PathChange::Updated => lsp::FileChangeType::CHANGED, - PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, - }; - Some(lsp::FileEvent { - uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(), - typ, - }) + { + let params = lsp::DidChangeWatchedFilesParams { + changes: changes + .iter() + .filter_map(|(path, _, change)| { + if !watched_paths.is_match(path) { + return None; + } + let typ = match change { + PathChange::Loaded => return None, + PathChange::Added => lsp::FileChangeType::CREATED, + PathChange::Removed => lsp::FileChangeType::DELETED, + PathChange::Updated => lsp::FileChangeType::CHANGED, + PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, + }; + Some(lsp::FileEvent { + uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(), + typ, }) - .collect(), - }; - if !params.changes.is_empty() { - server - .notify::<lsp::notification::DidChangeWatchedFiles>(¶ms) - .ok(); - } + }) + .collect(), + }; + if !params.changes.is_empty() { + server + .notify::<lsp::notification::DidChangeWatchedFiles>(¶ms) + .ok(); } } } @@ -11820,8 +11791,40 @@ impl LspStore { .transpose()? { server.update_capabilities(|capabilities| { + let mut sync_options = + Self::take_text_document_sync_options(capabilities); + sync_options.change = Some(sync_kind); capabilities.text_document_sync = - Some(lsp::TextDocumentSyncCapability::Kind(sync_kind)); + Some(lsp::TextDocumentSyncCapability::Options(sync_options)); + }); + notify_server_capabilities_updated(&server, cx); + } + } + "textDocument/didSave" => { + if let Some(include_text) = reg + .register_options + .map(|opts| { + let transpose = opts + .get("includeText") + .cloned() + .map(serde_json::from_value::<Option<bool>>) + .transpose(); + match transpose { + Ok(value) => Ok(value.flatten()), + Err(e) => Err(e), + } + }) + .transpose()? + { + server.update_capabilities(|capabilities| { + let mut sync_options = + Self::take_text_document_sync_options(capabilities); + sync_options.save = + Some(TextDocumentSyncSaveOptions::SaveOptions(lsp::SaveOptions { + include_text, + })); + capabilities.text_document_sync = + Some(lsp::TextDocumentSyncCapability::Options(sync_options)); }); notify_server_capabilities_updated(&server, cx); } @@ -11973,7 +11976,19 @@ impl LspStore { } "textDocument/didChange" => { server.update_capabilities(|capabilities| { - capabilities.text_document_sync = None; + let mut sync_options = Self::take_text_document_sync_options(capabilities); + sync_options.change = None; + capabilities.text_document_sync = + Some(lsp::TextDocumentSyncCapability::Options(sync_options)); + }); + notify_server_capabilities_updated(&server, cx); + } + "textDocument/didSave" => { + server.update_capabilities(|capabilities| { + let mut sync_options = Self::take_text_document_sync_options(capabilities); + sync_options.save = None; + capabilities.text_document_sync = + Some(lsp::TextDocumentSyncCapability::Options(sync_options)); }); notify_server_capabilities_updated(&server, cx); } @@ -12001,18 +12016,31 @@ impl LspStore { Ok(()) } + + fn take_text_document_sync_options( + capabilities: &mut lsp::ServerCapabilities, + ) -> lsp::TextDocumentSyncOptions { + match capabilities.text_document_sync.take() { + Some(lsp::TextDocumentSyncCapability::Options(sync_options)) => sync_options, + Some(lsp::TextDocumentSyncCapability::Kind(sync_kind)) => { + let mut sync_options = lsp::TextDocumentSyncOptions::default(); + sync_options.change = Some(sync_kind); + sync_options + } + None => lsp::TextDocumentSyncOptions::default(), + } + } } -// Registration with empty capabilities should be ignored. -// https://github.com/microsoft/vscode-languageserver-node/blob/d90a87f9557a0df9142cfb33e251cfa6fe27d970/client/src/common/formatting.ts#L67-L70 +// Registration with registerOptions as null, should fallback to true. +// https://github.com/microsoft/vscode-languageserver-node/blob/d90a87f9557a0df9142cfb33e251cfa6fe27d970/client/src/common/client.ts#L2133 fn parse_register_capabilities<T: serde::de::DeserializeOwned>( reg: lsp::Registration, ) -> anyhow::Result<Option<OneOf<bool, T>>> { - Ok(reg - .register_options - .map(|options| serde_json::from_value::<T>(options)) - .transpose()? - .map(OneOf::Right)) + Ok(match reg.register_options { + Some(options) => Some(OneOf::Right(serde_json::from_value::<T>(options)?)), + None => Some(OneOf::Left(true)), + }) } fn subscribe_to_binary_statuses( @@ -12371,7 +12399,7 @@ impl TryFrom<&FileOperationFilter> for RenameActionPredicate { ops.pattern .options .as_ref() - .map_or(false, |ops| ops.ignore_case.unwrap_or(false)), + .is_some_and(|ops| ops.ignore_case.unwrap_or(false)), ) .build()? .compile_matcher(), @@ -12386,7 +12414,7 @@ struct RenameActionPredicate { impl RenameActionPredicate { // Returns true if language server should be notified fn eval(&self, path: &str, is_dir: bool) -> bool { - self.kind.as_ref().map_or(true, |kind| { + self.kind.as_ref().is_none_or(|kind| { let expected_kind = if is_dir { FileOperationPatternKind::Folder } else { @@ -13103,24 +13131,18 @@ async fn populate_labels_for_symbols( fn include_text(server: &lsp::LanguageServer) -> Option<bool> { match server.capabilities().text_document_sync.as_ref()? { - lsp::TextDocumentSyncCapability::Kind(kind) => match *kind { - lsp::TextDocumentSyncKind::NONE => None, - lsp::TextDocumentSyncKind::FULL => Some(true), - lsp::TextDocumentSyncKind::INCREMENTAL => Some(false), - _ => None, - }, - lsp::TextDocumentSyncCapability::Options(options) => match options.save.as_ref()? { - lsp::TextDocumentSyncSaveOptions::Supported(supported) => { - if *supported { - Some(true) - } else { - None - } - } + lsp::TextDocumentSyncCapability::Options(opts) => match opts.save.as_ref()? { + // Server wants didSave but didn't specify includeText. + lsp::TextDocumentSyncSaveOptions::Supported(true) => Some(false), + // Server doesn't want didSave at all. + lsp::TextDocumentSyncSaveOptions::Supported(false) => None, + // Server provided SaveOptions. lsp::TextDocumentSyncSaveOptions::SaveOptions(save_options) => { Some(save_options.include_text.unwrap_or(false)) } }, + // We do not have any save info. Kind affects didChange only. + lsp::TextDocumentSyncCapability::Kind(_) => None, } } diff --git a/crates/project/src/lsp_store/lsp_ext_command.rs b/crates/project/src/lsp_store/lsp_ext_command.rs index cb13fa5efc..1c969f8114 100644 --- a/crates/project/src/lsp_store/lsp_ext_command.rs +++ b/crates/project/src/lsp_store/lsp_ext_command.rs @@ -115,14 +115,14 @@ impl LspCommand for ExpandMacro { message: Self::ProtoRequest, _: Entity<LspStore>, buffer: Entity<Buffer>, - mut cx: AsyncApp, + cx: AsyncApp, ) -> anyhow::Result<Self> { let position = message .position .and_then(deserialize_anchor) .context("invalid position")?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -249,14 +249,14 @@ impl LspCommand for OpenDocs { message: Self::ProtoRequest, _: Entity<LspStore>, buffer: Entity<Buffer>, - mut cx: AsyncApp, + cx: AsyncApp, ) -> anyhow::Result<Self> { let position = message .position .and_then(deserialize_anchor) .context("invalid position")?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } @@ -462,14 +462,14 @@ impl LspCommand for GoToParentModule { request: Self::ProtoRequest, _: Entity<LspStore>, buffer: Entity<Buffer>, - mut cx: AsyncApp, + cx: AsyncApp, ) -> anyhow::Result<Self> { let position = request .position .and_then(deserialize_anchor) .context("bad request with bad position")?; Ok(Self { - position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?, }) } diff --git a/crates/project/src/manifest_tree.rs b/crates/project/src/manifest_tree.rs index 8621d24d06..ced9b34d93 100644 --- a/crates/project/src/manifest_tree.rs +++ b/crates/project/src/manifest_tree.rs @@ -43,12 +43,9 @@ impl WorktreeRoots { match event { WorktreeEvent::UpdatedEntries(changes) => { for (path, _, kind) in changes.iter() { - match kind { - worktree::PathChange::Removed => { - let path = TriePath::from(path.as_ref()); - this.roots.remove(&path); - } - _ => {} + if kind == &worktree::PathChange::Removed { + let path = TriePath::from(path.as_ref()); + this.roots.remove(&path); } } } @@ -197,11 +194,8 @@ impl ManifestTree { evt: &WorktreeStoreEvent, _: &mut Context<Self>, ) { - match evt { - WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => { - self.root_points.remove(&worktree_id); - } - _ => {} + if let WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) = evt { + self.root_points.remove(worktree_id); } } } @@ -218,10 +212,8 @@ impl ManifestQueryDelegate { impl ManifestDelegate for ManifestQueryDelegate { fn exists(&self, path: &Path, is_dir: Option<bool>) -> bool { - self.worktree.entry_for_path(path).map_or(false, |entry| { - is_dir.map_or(true, |is_required_to_be_dir| { - is_required_to_be_dir == entry.is_dir() - }) + self.worktree.entry_for_path(path).is_some_and(|entry| { + is_dir.is_none_or(|is_required_to_be_dir| is_required_to_be_dir == entry.is_dir()) }) } diff --git a/crates/project/src/manifest_tree/path_trie.rs b/crates/project/src/manifest_tree/path_trie.rs index 1a0736765a..16110463ac 100644 --- a/crates/project/src/manifest_tree/path_trie.rs +++ b/crates/project/src/manifest_tree/path_trie.rs @@ -84,11 +84,11 @@ impl<Label: Ord + Clone> RootPathTrie<Label> { ) { let mut current = self; for key in path.0.iter() { - if !current.labels.is_empty() { - if (callback)(¤t.worktree_relative_path, ¤t.labels).is_break() { - return; - }; - } + if !current.labels.is_empty() + && (callback)(¤t.worktree_relative_path, ¤t.labels).is_break() + { + return; + }; current = match current.children.get(key) { Some(child) => child, None => return, diff --git a/crates/project/src/manifest_tree/server_tree.rs b/crates/project/src/manifest_tree/server_tree.rs index 49c0cff730..f5fd481324 100644 --- a/crates/project/src/manifest_tree/server_tree.rs +++ b/crates/project/src/manifest_tree/server_tree.rs @@ -192,7 +192,7 @@ impl LanguageServerTree { ) }); languages.insert(language_name.clone()); - Arc::downgrade(&node).into() + Arc::downgrade(node).into() }) } @@ -245,7 +245,7 @@ impl LanguageServerTree { if !settings.enable_language_server { return Default::default(); } - let available_lsp_adapters = self.languages.lsp_adapters(&language_name); + let available_lsp_adapters = self.languages.lsp_adapters(language_name); let available_language_servers = available_lsp_adapters .iter() .map(|lsp_adapter| lsp_adapter.name.clone()) @@ -287,7 +287,7 @@ impl LanguageServerTree { // (e.g., native vs extension) still end up in the right order at the end, rather than // it being based on which language server happened to be loaded in first. self.languages.reorder_language_servers( - &language_name, + language_name, adapters_with_settings .values() .map(|(_, adapter)| adapter.clone()) @@ -314,7 +314,7 @@ impl LanguageServerTree { pub(crate) fn remove_nodes(&mut self, ids: &BTreeSet<LanguageServerId>) { for (_, servers) in &mut self.instances { for (_, nodes) in &mut servers.roots { - nodes.retain(|_, (node, _)| node.id.get().map_or(true, |id| !ids.contains(&id))); + nodes.retain(|_, (node, _)| node.id.get().is_none_or(|id| !ids.contains(id))); } } } diff --git a/crates/project/src/prettier_store.rs b/crates/project/src/prettier_store.rs index 29997545cd..3ae5dc24ae 100644 --- a/crates/project/src/prettier_store.rs +++ b/crates/project/src/prettier_store.rs @@ -590,8 +590,8 @@ impl PrettierStore { new_plugins.clear(); } let mut needs_install = should_write_prettier_server_file(fs.as_ref()).await; - if let Some(previous_installation_task) = previous_installation_task { - if let Err(e) = previous_installation_task.await { + if let Some(previous_installation_task) = previous_installation_task + && let Err(e) = previous_installation_task.await { log::error!("Failed to install default prettier: {e:#}"); prettier_store.update(cx, |prettier_store, _| { if let PrettierInstallation::NotInstalled { attempts, not_installed_plugins, .. } = &mut prettier_store.default_prettier.prettier { @@ -601,8 +601,7 @@ impl PrettierStore { needs_install = true; }; })?; - } - }; + }; if installation_attempt > prettier::FAIL_THRESHOLD { prettier_store.update(cx, |prettier_store, _| { if let PrettierInstallation::NotInstalled { installation_task, .. } = &mut prettier_store.default_prettier.prettier { @@ -679,13 +678,13 @@ impl PrettierStore { ) { let mut prettier_plugins_by_worktree = HashMap::default(); for (worktree, language_settings) in language_formatters_to_check { - if language_settings.prettier.allowed { - if let Some(plugins) = prettier_plugins_for_language(&language_settings) { - prettier_plugins_by_worktree - .entry(worktree) - .or_insert_with(HashSet::default) - .extend(plugins.iter().cloned()); - } + if language_settings.prettier.allowed + && let Some(plugins) = prettier_plugins_for_language(&language_settings) + { + prettier_plugins_by_worktree + .entry(worktree) + .or_insert_with(HashSet::default) + .extend(plugins.iter().cloned()); } } for (worktree, prettier_plugins) in prettier_plugins_by_worktree { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 57afaceeca..f07ee13866 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -489,67 +489,63 @@ impl CompletionSource { .. } = self { - if apply_defaults { - if let Some(lsp_defaults) = lsp_defaults { - let mut completion_with_defaults = *lsp_completion.clone(); - let default_commit_characters = lsp_defaults.commit_characters.as_ref(); - let default_edit_range = lsp_defaults.edit_range.as_ref(); - let default_insert_text_format = lsp_defaults.insert_text_format.as_ref(); - let default_insert_text_mode = lsp_defaults.insert_text_mode.as_ref(); + if apply_defaults && let Some(lsp_defaults) = lsp_defaults { + let mut completion_with_defaults = *lsp_completion.clone(); + let default_commit_characters = lsp_defaults.commit_characters.as_ref(); + let default_edit_range = lsp_defaults.edit_range.as_ref(); + let default_insert_text_format = lsp_defaults.insert_text_format.as_ref(); + let default_insert_text_mode = lsp_defaults.insert_text_mode.as_ref(); - if default_commit_characters.is_some() - || default_edit_range.is_some() - || default_insert_text_format.is_some() - || default_insert_text_mode.is_some() + if default_commit_characters.is_some() + || default_edit_range.is_some() + || default_insert_text_format.is_some() + || default_insert_text_mode.is_some() + { + if completion_with_defaults.commit_characters.is_none() + && default_commit_characters.is_some() { - if completion_with_defaults.commit_characters.is_none() - && default_commit_characters.is_some() - { - completion_with_defaults.commit_characters = - default_commit_characters.cloned() - } - if completion_with_defaults.text_edit.is_none() { - match default_edit_range { - Some(lsp::CompletionListItemDefaultsEditRange::Range(range)) => { - completion_with_defaults.text_edit = - Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: *range, - new_text: completion_with_defaults.label.clone(), - })) - } - Some( - lsp::CompletionListItemDefaultsEditRange::InsertAndReplace { - insert, - replace, - }, - ) => { - completion_with_defaults.text_edit = - Some(lsp::CompletionTextEdit::InsertAndReplace( - lsp::InsertReplaceEdit { - new_text: completion_with_defaults.label.clone(), - insert: *insert, - replace: *replace, - }, - )) - } - None => {} + completion_with_defaults.commit_characters = + default_commit_characters.cloned() + } + if completion_with_defaults.text_edit.is_none() { + match default_edit_range { + Some(lsp::CompletionListItemDefaultsEditRange::Range(range)) => { + completion_with_defaults.text_edit = + Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: *range, + new_text: completion_with_defaults.label.clone(), + })) } - } - if completion_with_defaults.insert_text_format.is_none() - && default_insert_text_format.is_some() - { - completion_with_defaults.insert_text_format = - default_insert_text_format.cloned() - } - if completion_with_defaults.insert_text_mode.is_none() - && default_insert_text_mode.is_some() - { - completion_with_defaults.insert_text_mode = - default_insert_text_mode.cloned() + Some(lsp::CompletionListItemDefaultsEditRange::InsertAndReplace { + insert, + replace, + }) => { + completion_with_defaults.text_edit = + Some(lsp::CompletionTextEdit::InsertAndReplace( + lsp::InsertReplaceEdit { + new_text: completion_with_defaults.label.clone(), + insert: *insert, + replace: *replace, + }, + )) + } + None => {} } } - return Some(Cow::Owned(completion_with_defaults)); + if completion_with_defaults.insert_text_format.is_none() + && default_insert_text_format.is_some() + { + completion_with_defaults.insert_text_format = + default_insert_text_format.cloned() + } + if completion_with_defaults.insert_text_mode.is_none() + && default_insert_text_mode.is_some() + { + completion_with_defaults.insert_text_mode = + default_insert_text_mode.cloned() + } } + return Some(Cow::Owned(completion_with_defaults)); } Some(Cow::Borrowed(lsp_completion)) } else { @@ -1350,14 +1346,13 @@ impl Project { }; // ssh -> local machine handlers - let ssh = ssh.read(cx); - ssh.subscribe_to_entity(SSH_PROJECT_ID, &cx.entity()); - ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store); - ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store); - ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.lsp_store); - ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.dap_store); - ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer); - ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.git_store); + ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &cx.entity()); + ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store); + ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store); + ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.lsp_store); + ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.dap_store); + ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer); + ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.git_store); ssh_proto.add_entity_message_handler(Self::handle_create_buffer_for_peer); ssh_proto.add_entity_message_handler(Self::handle_update_worktree); @@ -1617,25 +1612,23 @@ impl Project { .into_iter() .map(|s| match s { EntitySubscription::BufferStore(subscription) => { - subscription.set_entity(&buffer_store, &mut cx) + subscription.set_entity(&buffer_store, &cx) } EntitySubscription::WorktreeStore(subscription) => { - subscription.set_entity(&worktree_store, &mut cx) + subscription.set_entity(&worktree_store, &cx) } EntitySubscription::GitStore(subscription) => { - subscription.set_entity(&git_store, &mut cx) + subscription.set_entity(&git_store, &cx) } EntitySubscription::SettingsObserver(subscription) => { - subscription.set_entity(&settings_observer, &mut cx) - } - EntitySubscription::Project(subscription) => { - subscription.set_entity(&this, &mut cx) + subscription.set_entity(&settings_observer, &cx) } + EntitySubscription::Project(subscription) => subscription.set_entity(&this, &cx), EntitySubscription::LspStore(subscription) => { - subscription.set_entity(&lsp_store, &mut cx) + subscription.set_entity(&lsp_store, &cx) } EntitySubscription::DapStore(subscription) => { - subscription.set_entity(&dap_store, &mut cx) + subscription.set_entity(&dap_store, &cx) } }) .collect::<Vec<_>>(); @@ -1848,7 +1841,7 @@ impl Project { cx: &'a mut App, ) -> Shared<Task<Option<HashMap<String, String>>>> { self.environment.update(cx, |environment, cx| { - environment.get_buffer_environment(&buffer, &worktree_store, cx) + environment.get_buffer_environment(buffer, worktree_store, cx) }) } @@ -1903,15 +1896,7 @@ impl Project { return true; } - return false; - } - - pub fn ssh_connection_string(&self, cx: &App) -> Option<SharedString> { - if let Some(ssh_state) = &self.ssh_client { - return Some(ssh_state.read(cx).connection_string().into()); - } - - return None; + false } pub fn ssh_connection_state(&self, cx: &App) -> Option<remote::ConnectionState> { @@ -2230,28 +2215,28 @@ impl Project { self.client_subscriptions.extend([ self.client .subscribe_to_entity(project_id)? - .set_entity(&cx.entity(), &mut cx.to_async()), + .set_entity(&cx.entity(), &cx.to_async()), self.client .subscribe_to_entity(project_id)? - .set_entity(&self.worktree_store, &mut cx.to_async()), + .set_entity(&self.worktree_store, &cx.to_async()), self.client .subscribe_to_entity(project_id)? - .set_entity(&self.buffer_store, &mut cx.to_async()), + .set_entity(&self.buffer_store, &cx.to_async()), self.client .subscribe_to_entity(project_id)? - .set_entity(&self.lsp_store, &mut cx.to_async()), + .set_entity(&self.lsp_store, &cx.to_async()), self.client .subscribe_to_entity(project_id)? - .set_entity(&self.settings_observer, &mut cx.to_async()), + .set_entity(&self.settings_observer, &cx.to_async()), self.client .subscribe_to_entity(project_id)? - .set_entity(&self.dap_store, &mut cx.to_async()), + .set_entity(&self.dap_store, &cx.to_async()), self.client .subscribe_to_entity(project_id)? - .set_entity(&self.breakpoint_store, &mut cx.to_async()), + .set_entity(&self.breakpoint_store, &cx.to_async()), self.client .subscribe_to_entity(project_id)? - .set_entity(&self.git_store, &mut cx.to_async()), + .set_entity(&self.git_store, &cx.to_async()), ]); self.buffer_store.update(cx, |buffer_store, cx| { @@ -2592,7 +2577,7 @@ impl Project { cx: &mut App, ) -> OpenLspBufferHandle { self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.register_buffer_with_language_servers(&buffer, HashSet::default(), false, cx) + lsp_store.register_buffer_with_language_servers(buffer, HashSet::default(), false, cx) }) } @@ -2755,11 +2740,12 @@ impl Project { operations, })) })?; - if let Some(request) = request { - if request.await.is_err() && !is_local { - *needs_resync_with_host = true; - break; - } + if let Some(request) = request + && request.await.is_err() + && !is_local + { + *needs_resync_with_host = true; + break; } } Ok(()) @@ -2890,14 +2876,11 @@ impl Project { event: &DapStoreEvent, cx: &mut Context<Self>, ) { - match event { - DapStoreEvent::Notification(message) => { - cx.emit(Event::Toast { - notification_id: "dap".into(), - message: message.clone(), - }); - } - _ => {} + if let DapStoreEvent::Notification(message) = event { + cx.emit(Event::Toast { + notification_id: "dap".into(), + message: message.clone(), + }); } } @@ -3184,14 +3167,11 @@ impl Project { event: &ImageItemEvent, cx: &mut Context<Self>, ) -> Option<()> { - match event { - ImageItemEvent::ReloadNeeded => { - if !self.is_via_collab() { - self.reload_images([image.clone()].into_iter().collect(), cx) - .detach_and_log_err(cx); - } - } - _ => {} + if let ImageItemEvent::ReloadNeeded = event + && !self.is_via_collab() + { + self.reload_images([image.clone()].into_iter().collect(), cx) + .detach_and_log_err(cx); } None @@ -3939,10 +3919,10 @@ impl Project { if let Some(entry) = b .entry_id(cx) .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx)) + && entry.is_ignored + && !search_query.include_ignored() { - if entry.is_ignored && !search_query.include_ignored() { - return false; - } + return false; } } true @@ -4139,7 +4119,7 @@ impl Project { } }) } else { - return Task::ready(None); + Task::ready(None) } } @@ -4151,11 +4131,11 @@ impl Project { ) -> Task<Option<ResolvedPath>> { let mut candidates = vec![path.clone()]; - if let Some(file) = buffer.read(cx).file() { - if let Some(dir) = file.path().parent() { - let joined = dir.to_path_buf().join(path); - candidates.push(joined); - } + if let Some(file) = buffer.read(cx).file() + && let Some(dir) = file.path().parent() + { + let joined = dir.to_path_buf().join(path); + candidates.push(joined); } let buffer_worktree_id = buffer.read(cx).file().map(|file| file.worktree_id(cx)); @@ -4167,18 +4147,15 @@ impl Project { }) .collect(); - cx.spawn(async move |_, mut cx| { - if let Some(buffer_worktree_id) = buffer_worktree_id { - if let Some((worktree, _)) = worktrees_with_ids + cx.spawn(async move |_, cx| { + if let Some(buffer_worktree_id) = buffer_worktree_id + && let Some((worktree, _)) = worktrees_with_ids .iter() .find(|(_, id)| *id == buffer_worktree_id) - { - for candidate in candidates.iter() { - if let Some(path) = - Self::resolve_path_in_worktree(&worktree, candidate, &mut cx) - { - return Some(path); - } + { + for candidate in candidates.iter() { + if let Some(path) = Self::resolve_path_in_worktree(worktree, candidate, cx) { + return Some(path); } } } @@ -4187,9 +4164,7 @@ impl Project { continue; } for candidate in candidates.iter() { - if let Some(path) = - Self::resolve_path_in_worktree(&worktree, candidate, &mut cx) - { + if let Some(path) = Self::resolve_path_in_worktree(&worktree, candidate, cx) { return Some(path); } } @@ -5197,7 +5172,7 @@ impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet { } fn prefix(&self) -> Arc<str> { - if self.snapshot.root_entry().map_or(false, |e| e.is_file()) { + if self.snapshot.root_entry().is_some_and(|e| e.is_file()) { self.snapshot.root_name().into() } else if self.include_root_name { format!("{}{}", self.snapshot.root_name(), std::path::MAIN_SEPARATOR).into() @@ -5329,7 +5304,7 @@ impl ResolvedPath { pub fn project_path(&self) -> Option<&ProjectPath> { match self { - Self::ProjectPath { project_path, .. } => Some(&project_path), + Self::ProjectPath { project_path, .. } => Some(project_path), _ => None, } } @@ -5399,7 +5374,7 @@ impl Completion { _ => None, }) .unwrap_or(DEFAULT_KIND_KEY); - (kind_key, &self.label.filter_text()) + (kind_key, self.label.filter_text()) } /// Whether this completion is a snippet. @@ -5407,7 +5382,7 @@ impl Completion { self.source // `lsp::CompletionListItemDefaults` has `insert_text_format` field .lsp_completion(true) - .map_or(false, |lsp_completion| { + .is_some_and(|lsp_completion| { lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET) }) } @@ -5463,9 +5438,10 @@ fn provide_inline_values( .collect::<String>(); let point = snapshot.offset_to_point(capture_range.end); - while scopes.last().map_or(false, |scope: &Range<_>| { - !scope.contains(&capture_range.start) - }) { + while scopes + .last() + .is_some_and(|scope: &Range<_>| !scope.contains(&capture_range.start)) + { scopes.pop(); } @@ -5475,7 +5451,7 @@ fn provide_inline_values( let scope = if scopes .last() - .map_or(true, |scope| !scope.contains(&active_debug_line_offset)) + .is_none_or(|scope| !scope.contains(&active_debug_line_offset)) { VariableScope::Global } else { diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index d78526ddd0..a6fea4059c 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -188,9 +188,9 @@ pub struct DiagnosticsSettings { impl DiagnosticsSettings { pub fn fetch_cargo_diagnostics(&self) -> bool { - self.cargo.as_ref().map_or(false, |cargo_diagnostics| { - cargo_diagnostics.fetch_cargo_diagnostics - }) + self.cargo + .as_ref() + .is_some_and(|cargo_diagnostics| cargo_diagnostics.fetch_cargo_diagnostics) } } @@ -1105,7 +1105,7 @@ impl SettingsObserver { cx: &mut Context<Self>, ) -> Task<()> { let mut user_tasks_file_rx = - watch_config_file(&cx.background_executor(), fs, file_path.clone()); + watch_config_file(cx.background_executor(), fs, file_path.clone()); let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next()); let weak_entry = cx.weak_entity(); cx.spawn(async move |settings_observer, cx| { @@ -1160,7 +1160,7 @@ impl SettingsObserver { cx: &mut Context<Self>, ) -> Task<()> { let mut user_tasks_file_rx = - watch_config_file(&cx.background_executor(), fs, file_path.clone()); + watch_config_file(cx.background_executor(), fs, file_path.clone()); let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next()); let weak_entry = cx.weak_entity(); cx.spawn(async move |settings_observer, cx| { diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 5b3827b42b..eb1e3828e9 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -695,7 +695,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { assert_eq!( buffer .completion_triggers() - .into_iter() + .iter() .cloned() .collect::<Vec<_>>(), &[".".to_string(), "::".to_string()] @@ -747,7 +747,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { assert_eq!( buffer .completion_triggers() - .into_iter() + .iter() .cloned() .collect::<Vec<_>>(), &[":".to_string()] @@ -766,7 +766,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { assert_eq!( buffer .completion_triggers() - .into_iter() + .iter() .cloned() .collect::<Vec<_>>(), &[".".to_string(), "::".to_string()] @@ -2947,9 +2947,10 @@ fn chunks_with_diagnostics<T: ToOffset + ToPoint>( ) -> Vec<(String, Option<DiagnosticSeverity>)> { let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new(); for chunk in buffer.snapshot().chunks(range, true) { - if chunks.last().map_or(false, |prev_chunk| { - prev_chunk.1 == chunk.diagnostic_severity - }) { + if chunks + .last() + .is_some_and(|prev_chunk| prev_chunk.1 == chunk.diagnostic_severity) + { chunks.last_mut().unwrap().0.push_str(chunk.text); } else { chunks.push((chunk.text.to_string(), chunk.diagnostic_severity)); diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index 4f024837c8..ee216a9976 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -155,16 +155,16 @@ impl SearchQuery { let initial_query = Arc::from(query.as_str()); if whole_word { let mut word_query = String::new(); - if let Some(first) = query.get(0..1) { - if WORD_MATCH_TEST.is_match(first).is_ok_and(|x| !x) { - word_query.push_str("\\b"); - } + if let Some(first) = query.get(0..1) + && WORD_MATCH_TEST.is_match(first).is_ok_and(|x| !x) + { + word_query.push_str("\\b"); } word_query.push_str(&query); - if let Some(last) = query.get(query.len() - 1..) { - if WORD_MATCH_TEST.is_match(last).is_ok_and(|x| !x) { - word_query.push_str("\\b"); - } + if let Some(last) = query.get(query.len() - 1..) + && WORD_MATCH_TEST.is_match(last).is_ok_and(|x| !x) + { + word_query.push_str("\\b"); } query = word_query } diff --git a/crates/project/src/search_history.rs b/crates/project/src/search_history.rs index 90b169bb0c..4b2a7a065b 100644 --- a/crates/project/src/search_history.rs +++ b/crates/project/src/search_history.rs @@ -45,20 +45,19 @@ impl SearchHistory { } pub fn add(&mut self, cursor: &mut SearchHistoryCursor, search_string: String) { - if self.insertion_behavior == QueryInsertionBehavior::ReplacePreviousIfContains { - if let Some(previously_searched) = self.history.back_mut() { - if search_string.contains(previously_searched.as_str()) { - *previously_searched = search_string; - cursor.selection = Some(self.history.len() - 1); - return; - } - } + if self.insertion_behavior == QueryInsertionBehavior::ReplacePreviousIfContains + && let Some(previously_searched) = self.history.back_mut() + && search_string.contains(previously_searched.as_str()) + { + *previously_searched = search_string; + cursor.selection = Some(self.history.len() - 1); + return; } - if let Some(max_history_len) = self.max_history_len { - if self.history.len() >= max_history_len { - self.history.pop_front(); - } + if let Some(max_history_len) = self.max_history_len + && self.history.len() >= max_history_len + { + self.history.pop_front(); } self.history.push_back(search_string); @@ -203,7 +202,7 @@ mod tests { assert_eq!(search_history.current(&cursor), Some("TypeScript")); cursor.reset(); - assert_eq!(search_history.current(&mut cursor), None); + assert_eq!(search_history.current(&cursor), None); assert_eq!( search_history.previous(&mut cursor), Some("TypeScript"), diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index d0f1c71daf..e51f8e0b3b 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -110,7 +110,7 @@ impl<T: InventoryContents> InventoryFor<T> { fn global_scenarios(&self) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> { self.global.iter().flat_map(|(file_path, templates)| { - templates.into_iter().map(|template| { + templates.iter().map(|template| { ( TaskSourceKind::AbsPath { id_base: Cow::Owned(format!("global {}", T::GLOBAL_SOURCE_FILE)), @@ -333,7 +333,7 @@ impl Inventory { for locator in locators.values() { if let Some(scenario) = locator - .create_scenario(&task.original_task(), &task.display_label(), &adapter) + .create_scenario(task.original_task(), task.display_label(), &adapter) .await { scenarios.push((kind, scenario)); diff --git a/crates/project/src/task_store.rs b/crates/project/src/task_store.rs index f6718a3f3c..ae49ce5b4d 100644 --- a/crates/project/src/task_store.rs +++ b/crates/project/src/task_store.rs @@ -71,7 +71,7 @@ impl TaskStore { .payload .location .context("no location given for task context handling")?; - let (buffer_store, is_remote) = store.read_with(&mut cx, |store, _| { + let (buffer_store, is_remote) = store.read_with(&cx, |store, _| { Ok(match store { TaskStore::Functional(state) => ( state.buffer_store.clone(), diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 5ea7b87fbe..b2556d7584 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -23,6 +23,13 @@ use util::{ paths::{PathStyle, RemotePathBuf}, }; +/// The directory inside a Python virtual environment that contains executables +const PYTHON_VENV_BIN_DIR: &str = if cfg!(target_os = "windows") { + "Scripts" +} else { + "bin" +}; + pub struct Terminals { pub(crate) local_handles: Vec<WeakEntity<terminal::Terminal>>, } @@ -60,13 +67,11 @@ pub struct SshDetails { impl Project { pub fn active_project_directory(&self, cx: &App) -> Option<Arc<Path>> { - let worktree = self - .active_entry() + self.active_entry() .and_then(|entry_id| self.worktree_for_entry(entry_id, cx)) .into_iter() .chain(self.worktrees(cx)) - .find_map(|tree| tree.read(cx).root_dir()); - worktree + .find_map(|tree| tree.read(cx).root_dir()) } pub fn first_project_directory(&self, cx: &App) -> Option<PathBuf> { @@ -92,7 +97,7 @@ impl Project { } } - return None; + None } pub fn create_terminal( @@ -112,13 +117,13 @@ impl Project { }; let mut settings_location = None; - if let Some(path) = path.as_ref() { - if let Some((worktree, _)) = self.find_worktree(path, cx) { - settings_location = Some(SettingsLocation { - worktree_id: worktree.read(cx).id(), - path, - }); - } + if let Some(path) = path.as_ref() + && let Some((worktree, _)) = self.find_worktree(path, cx) + { + settings_location = Some(SettingsLocation { + worktree_id: worktree.read(cx).id(), + path, + }); } let venv = TerminalSettings::get(settings_location, cx) .detect_venv @@ -144,13 +149,13 @@ impl Project { cx: &'a App, ) -> &'a TerminalSettings { let mut settings_location = None; - if let Some(path) = path.as_ref() { - if let Some((worktree, _)) = self.find_worktree(path, cx) { - settings_location = Some(SettingsLocation { - worktree_id: worktree.read(cx).id(), - path, - }); - } + if let Some(path) = path.as_ref() + && let Some((worktree, _)) = self.find_worktree(path, cx) + { + settings_location = Some(SettingsLocation { + worktree_id: worktree.read(cx).id(), + path, + }); } TerminalSettings::get(settings_location, cx) } @@ -232,13 +237,13 @@ impl Project { let is_ssh_terminal = ssh_details.is_some(); let mut settings_location = None; - if let Some(path) = path.as_ref() { - if let Some((worktree, _)) = this.find_worktree(path, cx) { - settings_location = Some(SettingsLocation { - worktree_id: worktree.read(cx).id(), - path, - }); - } + if let Some(path) = path.as_ref() + && let Some((worktree, _)) = this.find_worktree(path, cx) + { + settings_location = Some(SettingsLocation { + worktree_id: worktree.read(cx).id(), + path, + }); } let settings = TerminalSettings::get(settings_location, cx).clone(); @@ -368,7 +373,8 @@ impl Project { } None => { if let Some(venv_path) = &python_venv_directory { - add_environment_path(&mut env, &venv_path.join("bin")).log_err(); + add_environment_path(&mut env, &venv_path.join(PYTHON_VENV_BIN_DIR)) + .log_err(); } let shell = if let Some(program) = spawn_task.command { @@ -478,16 +484,12 @@ impl Project { venv_settings: &terminal_settings::VenvSettingsContent, cx: &App, ) -> Option<PathBuf> { - let bin_dir_name = match std::env::consts::OS { - "windows" => "Scripts", - _ => "bin", - }; venv_settings .directories .iter() .map(|name| abs_path.join(name)) .find(|venv_path| { - let bin_path = venv_path.join(bin_dir_name); + let bin_path = venv_path.join(PYTHON_VENV_BIN_DIR); self.find_worktree(&bin_path, cx) .and_then(|(worktree, relative_path)| { worktree.read(cx).entry_for_path(&relative_path) @@ -504,21 +506,17 @@ impl Project { ) -> Option<PathBuf> { let (worktree, _) = self.find_worktree(abs_path, cx)?; let fs = worktree.read(cx).as_local()?.fs(); - let bin_dir_name = match std::env::consts::OS { - "windows" => "Scripts", - _ => "bin", - }; venv_settings .directories .iter() .map(|name| abs_path.join(name)) .find(|venv_path| { - let bin_path = venv_path.join(bin_dir_name); + let bin_path = venv_path.join(PYTHON_VENV_BIN_DIR); // One-time synchronous check is acceptable for terminal/task initialization smol::block_on(fs.metadata(&bin_path)) .ok() .flatten() - .map_or(false, |meta| meta.is_dir) + .is_some_and(|meta| meta.is_dir) }) } @@ -589,10 +587,7 @@ impl Project { if venv_settings.venv_name.is_empty() { let path = venv_base_directory - .join(match std::env::consts::OS { - "windows" => "Scripts", - _ => "bin", - }) + .join(PYTHON_VENV_BIN_DIR) .join(activate_script_name) .to_string_lossy() .to_string(); @@ -668,11 +663,11 @@ pub fn wrap_for_ssh( env_changes.push_str(&format!("{}={} ", k, v)); } } - if let Some(venv_directory) = venv_directory { - if let Ok(str) = shlex::try_quote(venv_directory.to_string_lossy().as_ref()) { - let path = RemotePathBuf::new(PathBuf::from(str.to_string()), path_style).to_string(); - env_changes.push_str(&format!("PATH={}:$PATH ", path)); - } + if let Some(venv_directory) = venv_directory + && let Ok(str) = shlex::try_quote(venv_directory.to_string_lossy().as_ref()) + { + let path = RemotePathBuf::new(PathBuf::from(str.to_string()), path_style).to_string(); + env_changes.push_str(&format!("PATH={}:$PATH ", path)); } let commands = if let Some(path) = path { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 92d01357ae..8988d268f6 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -498,7 +498,7 @@ impl ProjectPanel { if let Some((worktree, expanded_dir_ids)) = project .read(cx) .worktree_for_id(*worktree_id, cx) - .zip(this.expanded_dir_ids.get_mut(&worktree_id)) + .zip(this.expanded_dir_ids.get_mut(worktree_id)) { let worktree = worktree.read(cx); @@ -558,7 +558,7 @@ impl ProjectPanel { if project_panel .edit_state .as_ref() - .map_or(false, |state| state.processing_filename.is_none()) + .is_some_and(|state| state.processing_filename.is_none()) { project_panel.edit_state = None; project_panel.update_visible_entries(None, cx); @@ -641,8 +641,8 @@ impl ProjectPanel { focus_opened_item, allow_preview, } => { - if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) { - if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { + if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) + && let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { let file_path = entry.path.clone(); let worktree_id = worktree.read(cx).id(); let entry_id = entry.id; @@ -692,11 +692,10 @@ impl ProjectPanel { } } } - } } &Event::SplitEntry { entry_id } => { - if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) { - if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { + if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) + && let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { workspace .split_path( ProjectPath { @@ -707,7 +706,6 @@ impl ProjectPanel { ) .detach_and_log_err(cx); } - } } _ => {} @@ -991,10 +989,10 @@ impl ProjectPanel { if let Some(parent_path) = entry.path.parent() { let snapshot = worktree.snapshot(); let mut child_entries = snapshot.child_entries(parent_path); - if let Some(child) = child_entries.next() { - if child_entries.next().is_none() { - return child.kind.is_dir(); - } + if let Some(child) = child_entries.next() + && child_entries.next().is_none() + { + return child.kind.is_dir(); } }; false @@ -1005,10 +1003,10 @@ impl ProjectPanel { let snapshot = worktree.snapshot(); let mut child_entries = snapshot.child_entries(&entry.path); - if let Some(child) = child_entries.next() { - if child_entries.next().is_none() { - return child.kind.is_dir(); - } + if let Some(child) = child_entries.next() + && child_entries.next().is_none() + { + return child.kind.is_dir(); } } false @@ -1021,12 +1019,12 @@ impl ProjectPanel { cx: &mut Context<Self>, ) { if let Some((worktree, entry)) = self.selected_entry(cx) { - if let Some(folded_ancestors) = self.ancestors.get_mut(&entry.id) { - if folded_ancestors.current_ancestor_depth > 0 { - folded_ancestors.current_ancestor_depth -= 1; - cx.notify(); - return; - } + if let Some(folded_ancestors) = self.ancestors.get_mut(&entry.id) + && folded_ancestors.current_ancestor_depth > 0 + { + folded_ancestors.current_ancestor_depth -= 1; + cx.notify(); + return; } if entry.is_dir() { let worktree_id = worktree.id(); @@ -1068,12 +1066,12 @@ impl ProjectPanel { fn collapse_entry(&mut self, entry: Entry, worktree: Entity<Worktree>, cx: &mut Context<Self>) { let worktree = worktree.read(cx); - if let Some(folded_ancestors) = self.ancestors.get_mut(&entry.id) { - if folded_ancestors.current_ancestor_depth + 1 < folded_ancestors.max_ancestor_depth() { - folded_ancestors.current_ancestor_depth += 1; - cx.notify(); - return; - } + if let Some(folded_ancestors) = self.ancestors.get_mut(&entry.id) + && folded_ancestors.current_ancestor_depth + 1 < folded_ancestors.max_ancestor_depth() + { + folded_ancestors.current_ancestor_depth += 1; + cx.notify(); + return; } let worktree_id = worktree.id(); let expanded_dir_ids = @@ -1126,23 +1124,23 @@ impl ProjectPanel { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) { - if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) { - self.project.update(cx, |project, cx| { - match expanded_dir_ids.binary_search(&entry_id) { - Ok(ix) => { - expanded_dir_ids.remove(ix); - } - Err(ix) => { - project.expand_entry(worktree_id, entry_id, cx); - expanded_dir_ids.insert(ix, entry_id); - } + if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) + && let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) + { + self.project.update(cx, |project, cx| { + match expanded_dir_ids.binary_search(&entry_id) { + Ok(ix) => { + expanded_dir_ids.remove(ix); } - }); - self.update_visible_entries(Some((worktree_id, entry_id)), cx); - window.focus(&self.focus_handle); - cx.notify(); - } + Err(ix) => { + project.expand_entry(worktree_id, entry_id, cx); + expanded_dir_ids.insert(ix, entry_id); + } + } + }); + self.update_visible_entries(Some((worktree_id, entry_id)), cx); + window.focus(&self.focus_handle); + cx.notify(); } } @@ -1152,20 +1150,20 @@ impl ProjectPanel { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) { - if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) { - match expanded_dir_ids.binary_search(&entry_id) { - Ok(_ix) => { - self.collapse_all_for_entry(worktree_id, entry_id, cx); - } - Err(_ix) => { - self.expand_all_for_entry(worktree_id, entry_id, cx); - } + if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) + && let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) + { + match expanded_dir_ids.binary_search(&entry_id) { + Ok(_ix) => { + self.collapse_all_for_entry(worktree_id, entry_id, cx); + } + Err(_ix) => { + self.expand_all_for_entry(worktree_id, entry_id, cx); } - self.update_visible_entries(Some((worktree_id, entry_id)), cx); - window.focus(&self.focus_handle); - cx.notify(); } + self.update_visible_entries(Some((worktree_id, entry_id)), cx); + window.focus(&self.focus_handle); + cx.notify(); } } @@ -1240,20 +1238,20 @@ impl ProjectPanel { } fn select_previous(&mut self, _: &SelectPrevious, window: &mut Window, cx: &mut Context<Self>) { - if let Some(edit_state) = &self.edit_state { - if edit_state.processing_filename.is_none() { - self.filename_editor.update(cx, |editor, cx| { - editor.move_to_beginning_of_line( - &editor::actions::MoveToBeginningOfLine { - stop_at_soft_wraps: false, - stop_at_indent: false, - }, - window, - cx, - ); - }); - return; - } + if let Some(edit_state) = &self.edit_state + && edit_state.processing_filename.is_none() + { + self.filename_editor.update(cx, |editor, cx| { + editor.move_to_beginning_of_line( + &editor::actions::MoveToBeginningOfLine { + stop_at_soft_wraps: false, + stop_at_indent: false, + }, + window, + cx, + ); + }); + return; } if let Some(selection) = self.selection { let (mut worktree_ix, mut entry_ix, _) = @@ -1330,39 +1328,37 @@ impl ProjectPanel { .project .read(cx) .worktree_for_id(edit_state.worktree_id, cx) + && let Some(entry) = worktree.read(cx).entry_for_id(edit_state.entry_id) { - if let Some(entry) = worktree.read(cx).entry_for_id(edit_state.entry_id) { - let mut already_exists = false; - if edit_state.is_new_entry() { - let new_path = entry.path.join(filename.trim_start_matches('/')); - if worktree - .read(cx) - .entry_for_path(new_path.as_path()) - .is_some() - { - already_exists = true; - } - } else { - let new_path = if let Some(parent) = entry.path.clone().parent() { - parent.join(&filename) - } else { - filename.clone().into() - }; - if let Some(existing) = worktree.read(cx).entry_for_path(new_path.as_path()) - { - if existing.id != entry.id { - already_exists = true; - } - } - }; - if already_exists { - edit_state.validation_state = ValidationState::Error(format!( - "File or directory '{}' already exists at location. Please choose a different name.", - filename - )); - cx.notify(); - return; + let mut already_exists = false; + if edit_state.is_new_entry() { + let new_path = entry.path.join(filename.trim_start_matches('/')); + if worktree + .read(cx) + .entry_for_path(new_path.as_path()) + .is_some() + { + already_exists = true; } + } else { + let new_path = if let Some(parent) = entry.path.clone().parent() { + parent.join(&filename) + } else { + filename.clone().into() + }; + if let Some(existing) = worktree.read(cx).entry_for_path(new_path.as_path()) + && existing.id != entry.id + { + already_exists = true; + } + }; + if already_exists { + edit_state.validation_state = ValidationState::Error(format!( + "File or directory '{}' already exists at location. Please choose a different name.", + filename + )); + cx.notify(); + return; } } let trimmed_filename = filename.trim(); @@ -1466,14 +1462,13 @@ impl ProjectPanel { } Ok(CreatedEntry::Included(new_entry)) => { project_panel.update( cx, |project_panel, cx| { - if let Some(selection) = &mut project_panel.selection { - if selection.entry_id == edited_entry_id { + if let Some(selection) = &mut project_panel.selection + && selection.entry_id == edited_entry_id { selection.worktree_id = worktree_id; selection.entry_id = new_entry.id; project_panel.marked_entries.clear(); project_panel.expand_to_selection(cx); } - } project_panel.update_visible_entries(None, cx); if is_new_entry && !is_dir { project_panel.open_entry(new_entry.id, true, false, cx); @@ -1606,11 +1601,11 @@ impl ProjectPanel { directory_id = entry.id; break; } else { - if let Some(parent_path) = entry.path.parent() { - if let Some(parent_entry) = worktree.entry_for_path(parent_path) { - entry = parent_entry; - continue; - } + if let Some(parent_path) = entry.path.parent() + && let Some(parent_entry) = worktree.entry_for_path(parent_path) + { + entry = parent_entry; + continue; } return; } @@ -1664,57 +1659,56 @@ impl ProjectPanel { worktree_id, entry_id, }) = self.selection + && let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) { - if let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) { - let sub_entry_id = self.unflatten_entry_id(entry_id); - if let Some(entry) = worktree.read(cx).entry_for_id(sub_entry_id) { - #[cfg(target_os = "windows")] - if Some(entry) == worktree.read(cx).root_entry() { + let sub_entry_id = self.unflatten_entry_id(entry_id); + if let Some(entry) = worktree.read(cx).entry_for_id(sub_entry_id) { + #[cfg(target_os = "windows")] + if Some(entry) == worktree.read(cx).root_entry() { + return; + } + + if Some(entry) == worktree.read(cx).root_entry() { + let settings = ProjectPanelSettings::get_global(cx); + let visible_worktrees_count = + self.project.read(cx).visible_worktrees(cx).count(); + if settings.hide_root && visible_worktrees_count == 1 { return; } - - if Some(entry) == worktree.read(cx).root_entry() { - let settings = ProjectPanelSettings::get_global(cx); - let visible_worktrees_count = - self.project.read(cx).visible_worktrees(cx).count(); - if settings.hide_root && visible_worktrees_count == 1 { - return; - } - } - - self.edit_state = Some(EditState { - worktree_id, - entry_id: sub_entry_id, - leaf_entry_id: Some(entry_id), - is_dir: entry.is_dir(), - processing_filename: None, - previously_focused: None, - depth: 0, - validation_state: ValidationState::None, - }); - let file_name = entry - .path - .file_name() - .map(|s| s.to_string_lossy()) - .unwrap_or_default() - .to_string(); - let selection = selection.unwrap_or_else(|| { - let file_stem = entry.path.file_stem().map(|s| s.to_string_lossy()); - let selection_end = - file_stem.map_or(file_name.len(), |file_stem| file_stem.len()); - 0..selection_end - }); - self.filename_editor.update(cx, |editor, cx| { - editor.set_text(file_name, window, cx); - editor.change_selections(Default::default(), window, cx, |s| { - s.select_ranges([selection]) - }); - window.focus(&editor.focus_handle(cx)); - }); - self.update_visible_entries(None, cx); - self.autoscroll(cx); - cx.notify(); } + + self.edit_state = Some(EditState { + worktree_id, + entry_id: sub_entry_id, + leaf_entry_id: Some(entry_id), + is_dir: entry.is_dir(), + processing_filename: None, + previously_focused: None, + depth: 0, + validation_state: ValidationState::None, + }); + let file_name = entry + .path + .file_name() + .map(|s| s.to_string_lossy()) + .unwrap_or_default() + .to_string(); + let selection = selection.unwrap_or_else(|| { + let file_stem = entry.path.file_stem().map(|s| s.to_string_lossy()); + let selection_end = + file_stem.map_or(file_name.len(), |file_stem| file_stem.len()); + 0..selection_end + }); + self.filename_editor.update(cx, |editor, cx| { + editor.set_text(file_name, window, cx); + editor.change_selections(Default::default(), window, cx, |s| { + s.select_ranges([selection]) + }); + window.focus(&editor.focus_handle(cx)); + }); + self.update_visible_entries(None, cx); + self.autoscroll(cx); + cx.notify(); } } } @@ -1820,10 +1814,10 @@ impl ProjectPanel { }; let next_selection = self.find_next_selection_after_deletion(items_to_delete, cx); cx.spawn_in(window, async move |panel, cx| { - if let Some(answer) = answer { - if answer.await != Ok(0) { - return anyhow::Ok(()); - } + if let Some(answer) = answer + && answer.await != Ok(0) + { + return anyhow::Ok(()); } for (entry_id, _) in file_paths { panel @@ -1988,19 +1982,19 @@ impl ProjectPanel { } fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) { - if let Some(edit_state) = &self.edit_state { - if edit_state.processing_filename.is_none() { - self.filename_editor.update(cx, |editor, cx| { - editor.move_to_end_of_line( - &editor::actions::MoveToEndOfLine { - stop_at_soft_wraps: false, - }, - window, - cx, - ); - }); - return; - } + if let Some(edit_state) = &self.edit_state + && edit_state.processing_filename.is_none() + { + self.filename_editor.update(cx, |editor, cx| { + editor.move_to_end_of_line( + &editor::actions::MoveToEndOfLine { + stop_at_soft_wraps: false, + }, + window, + cx, + ); + }); + return; } if let Some(selection) = self.selection { let (mut worktree_ix, mut entry_ix, _) = @@ -2021,20 +2015,19 @@ impl ProjectPanel { entries, .. }) = self.visible_entries.get(worktree_ix) + && let Some(entry) = entries.get(entry_ix) { - if let Some(entry) = entries.get(entry_ix) { - let selection = SelectedEntry { - worktree_id: *worktree_id, - entry_id: entry.id, - }; - self.selection = Some(selection); - if window.modifiers().shift { - self.marked_entries.push(selection); - } - - self.autoscroll(cx); - cx.notify(); + let selection = SelectedEntry { + worktree_id: *worktree_id, + entry_id: entry.id, + }; + self.selection = Some(selection); + if window.modifiers().shift { + self.marked_entries.push(selection); } + + self.autoscroll(cx); + cx.notify(); } } else { self.select_first(&SelectFirst {}, window, cx); @@ -2263,19 +2256,18 @@ impl ProjectPanel { entries, .. }) = self.visible_entries.first() + && let Some(entry) = entries.first() { - if let Some(entry) = entries.first() { - let selection = SelectedEntry { - worktree_id: *worktree_id, - entry_id: entry.id, - }; - self.selection = Some(selection); - if window.modifiers().shift { - self.marked_entries.push(selection); - } - self.autoscroll(cx); - cx.notify(); + let selection = SelectedEntry { + worktree_id: *worktree_id, + entry_id: entry.id, + }; + self.selection = Some(selection); + if window.modifiers().shift { + self.marked_entries.push(selection); } + self.autoscroll(cx); + cx.notify(); } } @@ -2936,10 +2928,10 @@ impl ProjectPanel { let Some(entry) = worktree.entry_for_path(path) else { continue; }; - if entry.is_dir() { - if let Err(idx) = expanded_dir_ids.binary_search(&entry.id) { - expanded_dir_ids.insert(idx, entry.id); - } + if entry.is_dir() + && let Err(idx) = expanded_dir_ids.binary_search(&entry.id) + { + expanded_dir_ids.insert(idx, entry.id); } } @@ -3013,15 +3005,16 @@ impl ProjectPanel { let mut new_entry_parent_id = None; let mut new_entry_kind = EntryKind::Dir; - if let Some(edit_state) = &self.edit_state { - if edit_state.worktree_id == worktree_id && edit_state.is_new_entry() { - new_entry_parent_id = Some(edit_state.entry_id); - new_entry_kind = if edit_state.is_dir { - EntryKind::Dir - } else { - EntryKind::File - }; - } + if let Some(edit_state) = &self.edit_state + && edit_state.worktree_id == worktree_id + && edit_state.is_new_entry() + { + new_entry_parent_id = Some(edit_state.entry_id); + new_entry_kind = if edit_state.is_dir { + EntryKind::Dir + } else { + EntryKind::File + }; } let mut visible_worktree_entries = Vec::new(); @@ -3032,7 +3025,7 @@ impl ProjectPanel { if hide_root && Some(entry.entry) == worktree.read(cx).root_entry() { if new_entry_parent_id == Some(entry.id) { visible_worktree_entries.push(Self::create_new_git_entry( - &entry.entry, + entry.entry, entry.git_summary, new_entry_kind, )); @@ -3043,19 +3036,18 @@ impl ProjectPanel { } if auto_collapse_dirs && entry.kind.is_dir() { auto_folded_ancestors.push(entry.id); - if !self.unfolded_dir_ids.contains(&entry.id) { - if let Some(root_path) = worktree_snapshot.root_entry() { - let mut child_entries = worktree_snapshot.child_entries(&entry.path); - if let Some(child) = child_entries.next() { - if entry.path != root_path.path - && child_entries.next().is_none() - && child.kind.is_dir() - { - entry_iter.advance(); + if !self.unfolded_dir_ids.contains(&entry.id) + && let Some(root_path) = worktree_snapshot.root_entry() + { + let mut child_entries = worktree_snapshot.child_entries(&entry.path); + if let Some(child) = child_entries.next() + && entry.path != root_path.path + && child_entries.next().is_none() + && child.kind.is_dir() + { + entry_iter.advance(); - continue; - } - } + continue; } } let depth = old_ancestors @@ -3063,10 +3055,10 @@ impl ProjectPanel { .map(|ancestor| ancestor.current_ancestor_depth) .unwrap_or_default() .min(auto_folded_ancestors.len()); - if let Some(edit_state) = &mut self.edit_state { - if edit_state.entry_id == entry.id { - edit_state.depth = depth; - } + if let Some(edit_state) = &mut self.edit_state + && edit_state.entry_id == entry.id + { + edit_state.depth = depth; } let mut ancestors = std::mem::take(&mut auto_folded_ancestors); if ancestors.len() > 1 { @@ -3088,14 +3080,14 @@ impl ProjectPanel { entry.id == new_entry_id || { self.ancestors .get(&entry.id) - .map_or(false, |entries| entries.ancestors.contains(&new_entry_id)) + .is_some_and(|entries| entries.ancestors.contains(&new_entry_id)) } } else { false }; if precedes_new_entry && (!hide_gitignore || !entry.is_ignored) { visible_worktree_entries.push(Self::create_new_git_entry( - &entry.entry, + entry.entry, entry.git_summary, new_entry_kind, )); @@ -3303,11 +3295,10 @@ impl ProjectPanel { ) })?.await?; - if answer == 1 { - if let Some(item_idx) = paths.iter().position(|p| p == original_path) { + if answer == 1 + && let Some(item_idx) = paths.iter().position(|p| p == original_path) { paths.remove(item_idx); } - } } if paths.is_empty() { @@ -3492,7 +3483,7 @@ impl ProjectPanel { let base_index = ix + entry_range.start; for (i, entry) in visible.entries[entry_range].iter().enumerate() { let global_index = base_index + i; - callback(&entry, global_index, entries, window, cx); + callback(entry, global_index, entries, window, cx); } ix = end_ix; } @@ -3587,7 +3578,7 @@ impl ProjectPanel { previous_components.next(); } - if let Some(_) = suffix_components { + if suffix_components.is_some() { new_path.push(previous_components); } if let Some(str) = new_path.to_str() { @@ -3972,7 +3963,7 @@ impl ProjectPanel { let is_marked = self.marked_entries.contains(&selection); let is_active = self .selection - .map_or(false, |selection| selection.entry_id == entry_id); + .is_some_and(|selection| selection.entry_id == entry_id); let file_name = details.filename.clone(); @@ -4179,7 +4170,7 @@ impl ProjectPanel { || this .expanded_dir_ids .get(&details.worktree_id) - .map_or(false, |ids| ids.binary_search(&entry_id).is_ok()) + .is_some_and(|ids| ids.binary_search(&entry_id).is_ok()) { return; } @@ -4298,8 +4289,8 @@ impl ProjectPanel { } } else if kind.is_dir() { project_panel.marked_entries.clear(); - if is_sticky { - if let Some((_, _, index)) = project_panel.index_for_entry(entry_id, worktree_id) { + if is_sticky + && let Some((_, _, index)) = project_panel.index_for_entry(entry_id, worktree_id) { project_panel.scroll_handle.scroll_to_item_with_offset(index, ScrollStrategy::Top, sticky_index.unwrap_or(0)); cx.notify(); // move down by 1px so that clicked item @@ -4314,7 +4305,6 @@ impl ProjectPanel { }); return; } - } if event.modifiers().alt { project_panel.toggle_expand_all(entry_id, window, cx); } else { @@ -4400,19 +4390,17 @@ impl ProjectPanel { } else { h_flex().child(Icon::from_path(icon.to_string()).color(Color::Muted)) } + } else if let Some((icon_name, color)) = + entry_diagnostic_aware_icon_name_and_color(diagnostic_severity) + { + h_flex() + .size(IconSize::default().rems()) + .child(Icon::new(icon_name).color(color).size(IconSize::Small)) } else { - if let Some((icon_name, color)) = - entry_diagnostic_aware_icon_name_and_color(diagnostic_severity) - { - h_flex() - .size(IconSize::default().rems()) - .child(Icon::new(icon_name).color(color).size(IconSize::Small)) - } else { - h_flex() - .size(IconSize::default().rems()) - .invisible() - .flex_none() - } + h_flex() + .size(IconSize::default().rems()) + .invisible() + .flex_none() }) .child( if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { @@ -4423,9 +4411,7 @@ impl ProjectPanel { let components = Path::new(&file_name) .components() .map(|comp| { - let comp_str = - comp.as_os_str().to_string_lossy().into_owned(); - comp_str + comp.as_os_str().to_string_lossy().into_owned() }) .collect::<Vec<_>>(); @@ -4464,7 +4450,7 @@ impl ProjectPanel { ); } else { let is_current_target = this.folded_directory_drag_target - .map_or(false, |target| + .is_some_and(|target| target.entry_id == entry_id && target.index == delimiter_target_index && target.is_delimiter_target @@ -4508,7 +4494,7 @@ impl ProjectPanel { } else { let is_current_target = this.folded_directory_drag_target .as_ref() - .map_or(false, |target| + .is_some_and(|target| target.entry_id == entry_id && target.index == index && !target.is_delimiter_target @@ -4527,7 +4513,7 @@ impl ProjectPanel { this.drag_onto(selections, target_entry_id, kind.is_file(), window, cx); } })) - .when(folded_directory_drag_target.map_or(false, |target| + .when(folded_directory_drag_target.is_some_and(|target| target.entry_id == entry_id && target.index == index ), |this| { @@ -4536,15 +4522,14 @@ impl ProjectPanel { }) }) .on_click(cx.listener(move |this, _, _, cx| { - if index != active_index { - if let Some(folds) = + if index != active_index + && let Some(folds) = this.ancestors.get_mut(&entry_id) { folds.current_ancestor_depth = components_len - 1 - index; cx.notify(); } - } })) .child( Label::new(component) @@ -4658,7 +4643,7 @@ impl ProjectPanel { }; let (depth, difference) = - ProjectPanel::calculate_depth_and_difference(&entry, entries_paths); + ProjectPanel::calculate_depth_and_difference(entry, entries_paths); let filename = match difference { diff if diff > 1 => entry @@ -4694,7 +4679,7 @@ impl ProjectPanel { let is_cut = self .clipboard .as_ref() - .map_or(false, |e| e.is_cut() && e.items().contains(&selection)); + .is_some_and(|e| e.is_cut() && e.items().contains(&selection)); EntryDetails { filename, @@ -4749,7 +4734,7 @@ impl ProjectPanel { if skip_ignored && worktree .entry_for_id(entry_id) - .map_or(true, |entry| entry.is_ignored && !entry.is_always_included) + .is_none_or(|entry| entry.is_ignored && !entry.is_always_included) { anyhow::bail!("can't reveal an ignored entry in the project panel"); } @@ -4880,12 +4865,12 @@ impl ProjectPanel { 'outer: loop { if let Some(parent_path) = current_path.parent() { for ancestor_path in parent_path.ancestors() { - if paths.contains(ancestor_path) { - if let Some(parent_entry) = worktree.entry_for_path(ancestor_path) { - sticky_parents.push(parent_entry.clone()); - current_path = parent_entry.path.clone(); - continue 'outer; - } + if paths.contains(ancestor_path) + && let Some(parent_entry) = worktree.entry_for_path(ancestor_path) + { + sticky_parents.push(parent_entry.clone()); + current_path = parent_entry.path.clone(); + continue 'outer; } } } @@ -5128,25 +5113,25 @@ impl Render for ProjectPanel { .on_action(cx.listener(Self::paste)) .on_action(cx.listener(Self::duplicate)) .on_click(cx.listener(|this, event: &gpui::ClickEvent, window, cx| { - if event.click_count() > 1 { - if let Some(entry_id) = this.last_worktree_root_id { - let project = this.project.read(cx); + if event.click_count() > 1 + && let Some(entry_id) = this.last_worktree_root_id + { + let project = this.project.read(cx); - let worktree_id = if let Some(worktree) = - project.worktree_for_entry(entry_id, cx) - { - worktree.read(cx).id() - } else { - return; - }; + let worktree_id = if let Some(worktree) = + project.worktree_for_entry(entry_id, cx) + { + worktree.read(cx).id() + } else { + return; + }; - this.selection = Some(SelectedEntry { - worktree_id, - entry_id, - }); + this.selection = Some(SelectedEntry { + worktree_id, + entry_id, + }); - this.new_file(&NewFile, window, cx); - } + this.new_file(&NewFile, window, cx); } })) }) @@ -5539,7 +5524,7 @@ impl Panel for ProjectPanel { project.visible_worktrees(cx).any(|tree| { tree.read(cx) .root_entry() - .map_or(false, |entry| entry.is_dir()) + .is_some_and(|entry| entry.is_dir()) }) } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 47aed8f470..9d0f54bc01 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -191,12 +191,12 @@ impl PickerDelegate for ProjectSymbolsDelegate { .iter() .enumerate() .map(|(id, symbol)| { - StringMatchCandidate::new(id, &symbol.label.filter_text()) + StringMatchCandidate::new(id, symbol.label.filter_text()) }) .partition(|candidate| { project .entry_for_path(&symbols[candidate.id].path, cx) - .map_or(false, |e| !e.is_ignored) + .is_some_and(|e| !e.is_ignored) }); delegate.visible_match_candidates = visible_match_candidates; diff --git a/crates/prompt_store/src/prompt_store.rs b/crates/prompt_store/src/prompt_store.rs index 06a65b97cd..fb087ce34d 100644 --- a/crates/prompt_store/src/prompt_store.rs +++ b/crates/prompt_store/src/prompt_store.rs @@ -247,9 +247,7 @@ impl PromptStore { if metadata_db .get(&txn, &prompt_id_v2)? - .map_or(true, |metadata_v2| { - metadata_v1.saved_at > metadata_v2.saved_at - }) + .is_none_or(|metadata_v2| metadata_v1.saved_at > metadata_v2.saved_at) { metadata_db.put( &mut txn, diff --git a/crates/prompt_store/src/prompts.rs b/crates/prompt_store/src/prompts.rs index 7eb63eec5e..4ab867ab64 100644 --- a/crates/prompt_store/src/prompts.rs +++ b/crates/prompt_store/src/prompts.rs @@ -229,12 +229,12 @@ impl PromptBuilder { log_message.push_str(" -> "); log_message.push_str(&target.display().to_string()); } - log::info!("{}.", log_message); + log::trace!("{}.", log_message); } else { if !found_dir_once { - log::info!("No prompt template overrides directory found at {}. Using built-in prompts.", templates_dir.display()); + log::trace!("No prompt template overrides directory found at {}. Using built-in prompts.", templates_dir.display()); if let Some(target) = symlink_status { - log::info!("Symlink found pointing to {}, but target is invalid.", target.display()); + log::trace!("Symlink found pointing to {}, but target is invalid.", target.display()); } } @@ -247,7 +247,7 @@ impl PromptBuilder { log_message.push_str(" -> "); log_message.push_str(&target.display().to_string()); } - log::info!("{}.", log_message); + log::trace!("{}.", log_message); break; } } @@ -261,13 +261,12 @@ impl PromptBuilder { // Initial scan of the prompt overrides directory if let Ok(mut entries) = params.fs.read_dir(&templates_dir).await { while let Some(Ok(file_path)) = entries.next().await { - if file_path.to_string_lossy().ends_with(".hbs") { - if let Ok(content) = params.fs.load(&file_path).await { + if file_path.to_string_lossy().ends_with(".hbs") + && let Ok(content) = params.fs.load(&file_path).await { let file_name = file_path.file_stem().unwrap().to_string_lossy(); log::debug!("Registering prompt template override: {}", file_name); handlebars.lock().register_template_string(&file_name, content).log_err(); } - } } } @@ -280,15 +279,14 @@ impl PromptBuilder { let mut combined_changes = futures::stream::select(changes, parent_changes); while let Some(changed_paths) = combined_changes.next().await { - if changed_paths.iter().any(|p| &p.path == &templates_dir) { - if !params.fs.is_dir(&templates_dir).await { + if changed_paths.iter().any(|p| &p.path == &templates_dir) + && !params.fs.is_dir(&templates_dir).await { log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates."); Self::register_built_in_templates(&mut handlebars.lock()).log_err(); break; } - } for event in changed_paths { - if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") { + if event.path.starts_with(&templates_dir) && event.path.extension().is_some_and(|ext| ext == "hbs") { log::info!("Reloading prompt template override: {}", event.path.display()); if let Some(content) = params.fs.load(&event.path).await.log_err() { let file_name = event.path.file_stem().unwrap().to_string_lossy(); @@ -311,12 +309,11 @@ impl PromptBuilder { .split('/') .next_back() .and_then(|s| s.strip_suffix(".hbs")) + && let Some(prompt) = Assets.load(path.as_ref()).log_err().flatten() { - if let Some(prompt) = Assets.load(path.as_ref()).log_err().flatten() { - log::debug!("Registering built-in prompt template: {}", id); - let prompt = String::from_utf8_lossy(prompt.as_ref()); - handlebars.register_template_string(id, LineEnding::normalize_cow(prompt))? - } + log::debug!("Registering built-in prompt template: {}", id); + let prompt = String::from_utf8_lossy(prompt.as_ref()); + handlebars.register_template_string(id, LineEnding::normalize_cow(prompt))? } } diff --git a/crates/proto/proto/git.proto b/crates/proto/proto/git.proto index f2c388a3a3..cfb0369875 100644 --- a/crates/proto/proto/git.proto +++ b/crates/proto/proto/git.proto @@ -121,6 +121,7 @@ message UpdateRepository { uint64 scan_id = 9; bool is_last_update = 10; optional GitCommitDetails head_commit_details = 11; + optional string merge_message = 12; } message RemoveRepository { diff --git a/crates/proto/src/error.rs b/crates/proto/src/error.rs index 7ba08df57d..1724a70217 100644 --- a/crates/proto/src/error.rs +++ b/crates/proto/src/error.rs @@ -190,10 +190,10 @@ impl ErrorExt for RpcError { fn error_tag(&self, k: &str) -> Option<&str> { for tag in &self.tags { let mut parts = tag.split('='); - if let Some(key) = parts.next() { - if key == k { - return parts.next(); - } + if let Some(key) = parts.next() + && key == k + { + return parts.next(); } } None diff --git a/crates/recent_projects/src/disconnected_overlay.rs b/crates/recent_projects/src/disconnected_overlay.rs index a6cd26355c..dd4d788cfd 100644 --- a/crates/recent_projects/src/disconnected_overlay.rs +++ b/crates/recent_projects/src/disconnected_overlay.rs @@ -37,7 +37,7 @@ impl ModalView for DisconnectedOverlay { _window: &mut Window, _: &mut Context<Self>, ) -> workspace::DismissDecision { - return workspace::DismissDecision::Dismiss(self.finished); + workspace::DismissDecision::Dismiss(self.finished) } fn fade_out_background(&self) -> bool { true @@ -88,11 +88,8 @@ impl DisconnectedOverlay { self.finished = true; cx.emit(DismissEvent); - match &self.host { - Host::SshRemoteProject(ssh_connection_options) => { - self.reconnect_to_ssh_remote(ssh_connection_options.clone(), window, cx); - } - _ => {} + if let Host::SshRemoteProject(ssh_connection_options) = &self.host { + self.reconnect_to_ssh_remote(ssh_connection_options.clone(), window, cx); } } diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 427ff21ce7..5a17fdfe42 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -663,10 +663,10 @@ impl RemoteServerProjects { let text = Some(state.editor.read(cx).text(cx)).filter(|text| !text.is_empty()); let index = state.index; self.update_settings_file(cx, move |setting, _| { - if let Some(connections) = setting.ssh_connections.as_mut() { - if let Some(connection) = connections.get_mut(index) { - connection.nickname = text; - } + if let Some(connections) = setting.ssh_connections.as_mut() + && let Some(connection) = connections.get_mut(index) + { + connection.nickname = text; } }); self.mode = Mode::default_mode(&self.ssh_config_servers, cx); @@ -1409,7 +1409,7 @@ impl RemoteServerProjects { if ssh_settings .ssh_connections .as_ref() - .map_or(false, |connections| { + .is_some_and(|connections| { state .servers .iter() diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 7b58792178..670fcb4800 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -436,7 +436,7 @@ impl ModalView for SshConnectionModal { _window: &mut Window, _: &mut Context<Self>, ) -> workspace::DismissDecision { - return workspace::DismissDecision::Dismiss(self.finished); + workspace::DismissDecision::Dismiss(self.finished) } fn fade_out_background(&self) -> bool { diff --git a/crates/refineable/derive_refineable/src/derive_refineable.rs b/crates/refineable/derive_refineable/src/derive_refineable.rs index 3f6b45cc12..ddf3855a4d 100644 --- a/crates/refineable/derive_refineable/src/derive_refineable.rs +++ b/crates/refineable/derive_refineable/src/derive_refineable.rs @@ -510,12 +510,12 @@ fn is_refineable_field(f: &Field) -> bool { } fn is_optional_field(f: &Field) -> bool { - if let Type::Path(typepath) = &f.ty { - if typepath.qself.is_none() { - let segments = &typepath.path.segments; - if segments.len() == 1 && segments.iter().any(|s| s.ident == "Option") { - return true; - } + if let Type::Path(typepath) = &f.ty + && typepath.qself.is_none() + { + let segments = &typepath.path.segments; + if segments.len() == 1 && segments.iter().any(|s| s.ident == "Option") { + return true; } } false diff --git a/crates/remote/src/protocol.rs b/crates/remote/src/protocol.rs index 787094781d..e5a9c5b7a5 100644 --- a/crates/remote/src/protocol.rs +++ b/crates/remote/src/protocol.rs @@ -31,8 +31,8 @@ pub async fn read_message<S: AsyncRead + Unpin>( stream.read_exact(buffer).await?; let len = message_len_from_buffer(buffer); - let result = read_message_with_len(stream, buffer, len).await; - result + + read_message_with_len(stream, buffer, len).await } pub async fn write_message<S: AsyncWrite + Unpin>( diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index ea383ac264..7173bc9b3b 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -26,8 +26,7 @@ use parking_lot::Mutex; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use rpc::{ - AnyProtoClient, EntityMessageSubscriber, ErrorExt, ProtoClient, ProtoMessageHandlerSet, - RpcError, + AnyProtoClient, ErrorExt, ProtoClient, ProtoMessageHandlerSet, RpcError, proto::{self, Envelope, EnvelopedMessage, PeerId, RequestMessage, build_typed_envelope}, }; use schemars::JsonSchema; @@ -37,7 +36,6 @@ use smol::{ process::{self, Child, Stdio}, }; use std::{ - any::TypeId, collections::VecDeque, fmt, iter, ops::ControlFlow, @@ -664,6 +662,7 @@ impl ConnectionIdentifier { pub fn setup() -> Self { Self::Setup(NEXT_ID.fetch_add(1, SeqCst)) } + // This string gets used in a socket name, and so must be relatively short. // The total length of: // /home/{username}/.local/share/zed/server_state/{name}/stdout.sock @@ -730,7 +729,7 @@ impl SshRemoteClient { cx, ); - let multiplex_task = Self::monitor(this.downgrade(), io_task, &cx); + let multiplex_task = Self::monitor(this.downgrade(), io_task, cx); if let Err(error) = client.ping(HEARTBEAT_TIMEOUT).await { log::error!("failed to establish connection: {}", error); @@ -760,6 +759,15 @@ impl SshRemoteClient { }) } + pub fn proto_client_from_channels( + incoming_rx: mpsc::UnboundedReceiver<Envelope>, + outgoing_tx: mpsc::UnboundedSender<Envelope>, + cx: &App, + name: &'static str, + ) -> AnyProtoClient { + ChannelClient::new(incoming_rx, outgoing_tx, cx, name).into() + } + pub fn shutdown_processes<T: RequestMessage>( &self, shutdown_request: Option<T>, @@ -918,8 +926,8 @@ impl SshRemoteClient { } }; - let multiplex_task = Self::monitor(this.clone(), io_task, &cx); - client.reconnect(incoming_rx, outgoing_tx, &cx); + let multiplex_task = Self::monitor(this.clone(), io_task, cx); + client.reconnect(incoming_rx, outgoing_tx, cx); if let Err(error) = client.resync(HEARTBEAT_TIMEOUT).await { failed!(error, attempts, ssh_connection, delegate); @@ -990,64 +998,63 @@ impl SshRemoteClient { }; cx.spawn(async move |cx| { - let mut missed_heartbeats = 0; + let mut missed_heartbeats = 0; - let keepalive_timer = cx.background_executor().timer(HEARTBEAT_INTERVAL).fuse(); - futures::pin_mut!(keepalive_timer); + let keepalive_timer = cx.background_executor().timer(HEARTBEAT_INTERVAL).fuse(); + futures::pin_mut!(keepalive_timer); - loop { - select_biased! { - result = connection_activity_rx.next().fuse() => { - if result.is_none() { - log::warn!("ssh heartbeat: connection activity channel has been dropped. stopping."); - return Ok(()); - } - - if missed_heartbeats != 0 { - missed_heartbeats = 0; - let _ =this.update(cx, |this, mut cx| { - this.handle_heartbeat_result(missed_heartbeats, &mut cx) - })?; - } + loop { + select_biased! { + result = connection_activity_rx.next().fuse() => { + if result.is_none() { + log::warn!("ssh heartbeat: connection activity channel has been dropped. stopping."); + return Ok(()); } - _ = keepalive_timer => { - log::debug!("Sending heartbeat to server..."); - let result = select_biased! { - _ = connection_activity_rx.next().fuse() => { - Ok(()) - } - ping_result = client.ping(HEARTBEAT_TIMEOUT).fuse() => { - ping_result - } - }; - - if result.is_err() { - missed_heartbeats += 1; - log::warn!( - "No heartbeat from server after {:?}. Missed heartbeat {} out of {}.", - HEARTBEAT_TIMEOUT, - missed_heartbeats, - MAX_MISSED_HEARTBEATS - ); - } else if missed_heartbeats != 0 { - missed_heartbeats = 0; - } else { - continue; - } - - let result = this.update(cx, |this, mut cx| { - this.handle_heartbeat_result(missed_heartbeats, &mut cx) + if missed_heartbeats != 0 { + missed_heartbeats = 0; + let _ =this.update(cx, |this, cx| { + this.handle_heartbeat_result(missed_heartbeats, cx) })?; - if result.is_break() { - return Ok(()); - } } } + _ = keepalive_timer => { + log::debug!("Sending heartbeat to server..."); - keepalive_timer.set(cx.background_executor().timer(HEARTBEAT_INTERVAL).fuse()); + let result = select_biased! { + _ = connection_activity_rx.next().fuse() => { + Ok(()) + } + ping_result = client.ping(HEARTBEAT_TIMEOUT).fuse() => { + ping_result + } + }; + + if result.is_err() { + missed_heartbeats += 1; + log::warn!( + "No heartbeat from server after {:?}. Missed heartbeat {} out of {}.", + HEARTBEAT_TIMEOUT, + missed_heartbeats, + MAX_MISSED_HEARTBEATS + ); + } else if missed_heartbeats != 0 { + missed_heartbeats = 0; + } else { + continue; + } + + let result = this.update(cx, |this, cx| { + this.handle_heartbeat_result(missed_heartbeats, cx) + })?; + if result.is_break() { + return Ok(()); + } + } } + keepalive_timer.set(cx.background_executor().timer(HEARTBEAT_INTERVAL).fuse()); + } }) } @@ -1119,7 +1126,7 @@ impl SshRemoteClient { } fn state_is(&self, check: impl FnOnce(&State) -> bool) -> bool { - self.state.lock().as_ref().map_or(false, check) + self.state.lock().as_ref().is_some_and(check) } fn try_set_state(&self, cx: &mut Context<Self>, map: impl FnOnce(&State) -> Option<State>) { @@ -1145,10 +1152,6 @@ impl SshRemoteClient { cx.notify(); } - pub fn subscribe_to_entity<E: 'static>(&self, remote_id: u64, entity: &Entity<E>) { - self.client.subscribe_to_entity(remote_id, entity); - } - pub fn ssh_info(&self) -> Option<(SshArgs, PathStyle)> { self.state .lock() @@ -1214,7 +1217,7 @@ impl SshRemoteClient { .await .unwrap(); - connection.simulate_disconnect(&cx); + connection.simulate_disconnect(cx); }) } @@ -1222,7 +1225,7 @@ impl SshRemoteClient { pub fn fake_server( client_cx: &mut gpui::TestAppContext, server_cx: &mut gpui::TestAppContext, - ) -> (SshConnectionOptions, Arc<ChannelClient>) { + ) -> (SshConnectionOptions, AnyProtoClient) { let port = client_cx .update(|cx| cx.default_global::<ConnectionPool>().connections.len() as u16 + 1); let opts = SshConnectionOptions { @@ -1255,7 +1258,7 @@ impl SshRemoteClient { }) }); - (opts, server_client) + (opts, server_client.into()) } #[cfg(any(test, feature = "test-support"))] @@ -1310,10 +1313,10 @@ impl ConnectionPool { return task.clone(); } Some(ConnectionPoolEntry::Connected(ssh)) => { - if let Some(ssh) = ssh.upgrade() { - if !ssh.has_been_killed() { - return Task::ready(Ok(ssh)).shared(); - } + if let Some(ssh) = ssh.upgrade() + && !ssh.has_been_killed() + { + return Task::ready(Ok(ssh)).shared(); } self.connections.remove(&opts); } @@ -1523,7 +1526,7 @@ impl RemoteConnection for SshRemoteConnection { incoming_tx, outgoing_rx, connection_activity_tx, - &cx, + cx, ) } @@ -1840,26 +1843,25 @@ impl SshRemoteConnection { )), self.ssh_path_style, ); - if !self.socket.connection_options.upload_binary_over_ssh { - if let Some((url, body)) = delegate + if !self.socket.connection_options.upload_binary_over_ssh + && let Some((url, body)) = delegate .get_download_params(self.ssh_platform, release_channel, wanted_version, cx) .await? + { + match self + .download_binary_on_server(&url, &body, &tmp_path_gz, delegate, cx) + .await { - match self - .download_binary_on_server(&url, &body, &tmp_path_gz, delegate, cx) - .await - { - Ok(_) => { - self.extract_server_binary(&dst_path, &tmp_path_gz, delegate, cx) - .await?; - return Ok(dst_path); - } - Err(e) => { - log::error!( - "Failed to download binary on server, attempting to upload server: {}", - e - ) - } + Ok(_) => { + self.extract_server_binary(&dst_path, &tmp_path_gz, delegate, cx) + .await?; + return Ok(dst_path); + } + Err(e) => { + log::error!( + "Failed to download binary on server, attempting to upload server: {}", + e + ) } } } @@ -1871,7 +1873,7 @@ impl SshRemoteConnection { .await?; self.extract_server_binary(&dst_path, &tmp_path_gz, delegate, cx) .await?; - return Ok(dst_path); + Ok(dst_path) } async fn download_binary_on_server( @@ -1908,8 +1910,8 @@ impl SshRemoteConnection { "-H", "Content-Type: application/json", "-d", - &body, - &url, + body, + url, "-o", &tmp_path_gz.to_string(), ], @@ -1930,8 +1932,8 @@ impl SshRemoteConnection { "--method=GET", "--header=Content-Type: application/json", "--body-data", - &body, - &url, + body, + url, "-O", &tmp_path_gz.to_string(), ], @@ -1982,7 +1984,7 @@ impl SshRemoteConnection { tmp_path_gz, size / 1024 ); - self.upload_file(&src_path, &tmp_path_gz) + self.upload_file(src_path, tmp_path_gz) .await .context("failed to upload server binary")?; log::info!("uploaded remote development server in {:?}", t0.elapsed()); @@ -2123,109 +2125,106 @@ impl SshRemoteConnection { .env("RUSTFLAGS", &rust_flags), ) .await?; + } else if build_remote_server.contains("cross") { + #[cfg(target_os = "windows")] + use util::paths::SanitizedPath; + + delegate.set_status(Some("Installing cross.rs for cross-compilation"), cx); + log::info!("installing cross"); + run_cmd(Command::new("cargo").args([ + "install", + "cross", + "--git", + "https://github.com/cross-rs/cross", + ])) + .await?; + + delegate.set_status( + Some(&format!( + "Building remote server binary from source for {} with Docker", + &triple + )), + cx, + ); + log::info!("building remote server binary from source for {}", &triple); + + // On Windows, the binding needs to be set to the canonical path + #[cfg(target_os = "windows")] + let src = + SanitizedPath::from(smol::fs::canonicalize("./target").await?).to_glob_string(); + #[cfg(not(target_os = "windows"))] + let src = "./target"; + run_cmd( + Command::new("cross") + .args([ + "build", + "--package", + "remote_server", + "--features", + "debug-embed", + "--target-dir", + "target/remote_server", + "--target", + &triple, + ]) + .env( + "CROSS_CONTAINER_OPTS", + format!("--mount type=bind,src={src},dst=/app/target"), + ) + .env("RUSTFLAGS", &rust_flags), + ) + .await?; } else { - if build_remote_server.contains("cross") { - #[cfg(target_os = "windows")] - use util::paths::SanitizedPath; + let which = cx + .background_spawn(async move { which::which("zig") }) + .await; - delegate.set_status(Some("Installing cross.rs for cross-compilation"), cx); - log::info!("installing cross"); - run_cmd(Command::new("cargo").args([ - "install", - "cross", - "--git", - "https://github.com/cross-rs/cross", - ])) - .await?; - - delegate.set_status( - Some(&format!( - "Building remote server binary from source for {} with Docker", - &triple - )), - cx, - ); - log::info!("building remote server binary from source for {}", &triple); - - // On Windows, the binding needs to be set to the canonical path - #[cfg(target_os = "windows")] - let src = - SanitizedPath::from(smol::fs::canonicalize("./target").await?).to_glob_string(); + if which.is_err() { #[cfg(not(target_os = "windows"))] - let src = "./target"; - run_cmd( - Command::new("cross") - .args([ - "build", - "--package", - "remote_server", - "--features", - "debug-embed", - "--target-dir", - "target/remote_server", - "--target", - &triple, - ]) - .env( - "CROSS_CONTAINER_OPTS", - format!("--mount type=bind,src={src},dst=/app/target"), - ) - .env("RUSTFLAGS", &rust_flags), - ) - .await?; - } else { - let which = cx - .background_spawn(async move { which::which("zig") }) - .await; - - if which.is_err() { - #[cfg(not(target_os = "windows"))] - { - anyhow::bail!( - "zig not found on $PATH, install zig (see https://ziglang.org/learn/getting-started or use zigup) or pass ZED_BUILD_REMOTE_SERVER=cross to use cross" - ) - } - #[cfg(target_os = "windows")] - { - anyhow::bail!( - "zig not found on $PATH, install zig (use `winget install -e --id zig.zig` or see https://ziglang.org/learn/getting-started or use zigup) or pass ZED_BUILD_REMOTE_SERVER=cross to use cross" - ) - } + { + anyhow::bail!( + "zig not found on $PATH, install zig (see https://ziglang.org/learn/getting-started or use zigup) or pass ZED_BUILD_REMOTE_SERVER=cross to use cross" + ) + } + #[cfg(target_os = "windows")] + { + anyhow::bail!( + "zig not found on $PATH, install zig (use `winget install -e --id zig.zig` or see https://ziglang.org/learn/getting-started or use zigup) or pass ZED_BUILD_REMOTE_SERVER=cross to use cross" + ) } - - delegate.set_status(Some("Adding rustup target for cross-compilation"), cx); - log::info!("adding rustup target"); - run_cmd(Command::new("rustup").args(["target", "add"]).arg(&triple)).await?; - - delegate.set_status(Some("Installing cargo-zigbuild for cross-compilation"), cx); - log::info!("installing cargo-zigbuild"); - run_cmd(Command::new("cargo").args(["install", "--locked", "cargo-zigbuild"])) - .await?; - - delegate.set_status( - Some(&format!( - "Building remote binary from source for {triple} with Zig" - )), - cx, - ); - log::info!("building remote binary from source for {triple} with Zig"); - run_cmd( - Command::new("cargo") - .args([ - "zigbuild", - "--package", - "remote_server", - "--features", - "debug-embed", - "--target-dir", - "target/remote_server", - "--target", - &triple, - ]) - .env("RUSTFLAGS", &rust_flags), - ) - .await?; } + + delegate.set_status(Some("Adding rustup target for cross-compilation"), cx); + log::info!("adding rustup target"); + run_cmd(Command::new("rustup").args(["target", "add"]).arg(&triple)).await?; + + delegate.set_status(Some("Installing cargo-zigbuild for cross-compilation"), cx); + log::info!("installing cargo-zigbuild"); + run_cmd(Command::new("cargo").args(["install", "--locked", "cargo-zigbuild"])).await?; + + delegate.set_status( + Some(&format!( + "Building remote binary from source for {triple} with Zig" + )), + cx, + ); + log::info!("building remote binary from source for {triple} with Zig"); + run_cmd( + Command::new("cargo") + .args([ + "zigbuild", + "--package", + "remote_server", + "--features", + "debug-embed", + "--target-dir", + "target/remote_server", + "--target", + &triple, + ]) + .env("RUSTFLAGS", &rust_flags), + ) + .await?; }; let bin_path = Path::new("target") .join("remote_server") @@ -2270,7 +2269,7 @@ impl SshRemoteConnection { type ResponseChannels = Mutex<HashMap<MessageId, oneshot::Sender<(Envelope, oneshot::Sender<()>)>>>; -pub struct ChannelClient { +struct ChannelClient { next_message_id: AtomicU32, outgoing_tx: Mutex<mpsc::UnboundedSender<Envelope>>, buffer: Mutex<VecDeque<Envelope>>, @@ -2282,7 +2281,7 @@ pub struct ChannelClient { } impl ChannelClient { - pub fn new( + fn new( incoming_rx: mpsc::UnboundedReceiver<Envelope>, outgoing_tx: mpsc::UnboundedSender<Envelope>, cx: &App, @@ -2403,7 +2402,7 @@ impl ChannelClient { }) } - pub fn reconnect( + fn reconnect( self: &Arc<Self>, incoming_rx: UnboundedReceiver<Envelope>, outgoing_tx: UnboundedSender<Envelope>, @@ -2413,26 +2412,7 @@ impl ChannelClient { *self.task.lock() = Self::start_handling_messages(Arc::downgrade(self), incoming_rx, cx); } - pub fn subscribe_to_entity<E: 'static>(&self, remote_id: u64, entity: &Entity<E>) { - let id = (TypeId::of::<E>(), remote_id); - - let mut message_handlers = self.message_handlers.lock(); - if message_handlers - .entities_by_type_and_remote_id - .contains_key(&id) - { - panic!("already subscribed to entity"); - } - - message_handlers.entities_by_type_and_remote_id.insert( - id, - EntityMessageSubscriber::Entity { - handle: entity.downgrade().into(), - }, - ); - } - - pub fn request<T: RequestMessage>( + fn request<T: RequestMessage>( &self, payload: T, ) -> impl 'static + Future<Output = Result<T::Response>> { @@ -2454,7 +2434,7 @@ impl ChannelClient { } } - pub async fn resync(&self, timeout: Duration) -> Result<()> { + async fn resync(&self, timeout: Duration) -> Result<()> { smol::future::or( async { self.request_internal(proto::FlushBufferedMessages {}, false) @@ -2476,7 +2456,7 @@ impl ChannelClient { .await } - pub async fn ping(&self, timeout: Duration) -> Result<()> { + async fn ping(&self, timeout: Duration) -> Result<()> { smol::future::or( async { self.request(proto::Ping {}).await?; @@ -2654,7 +2634,7 @@ mod fake { let (outgoing_tx, _) = mpsc::unbounded::<Envelope>(); let (_, incoming_rx) = mpsc::unbounded::<Envelope>(); self.server_channel - .reconnect(incoming_rx, outgoing_tx, &self.server_cx.get(&cx)); + .reconnect(incoming_rx, outgoing_tx, &self.server_cx.get(cx)); } fn start_proxy( diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index ac1737ba4b..3bcdcbd73c 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -19,7 +19,6 @@ use project::{ task_store::TaskStore, worktree_store::WorktreeStore, }; -use remote::ssh_session::ChannelClient; use rpc::{ AnyProtoClient, TypedEnvelope, proto::{self, SSH_PEER_ID, SSH_PROJECT_ID}, @@ -50,7 +49,7 @@ pub struct HeadlessProject { } pub struct HeadlessAppState { - pub session: Arc<ChannelClient>, + pub session: AnyProtoClient, pub fs: Arc<dyn Fs>, pub http_client: Arc<dyn HttpClient>, pub node_runtime: NodeRuntime, @@ -81,7 +80,7 @@ impl HeadlessProject { let worktree_store = cx.new(|cx| { let mut store = WorktreeStore::local(true, fs.clone()); - store.shared(SSH_PROJECT_ID, session.clone().into(), cx); + store.shared(SSH_PROJECT_ID, session.clone(), cx); store }); @@ -99,7 +98,7 @@ impl HeadlessProject { let buffer_store = cx.new(|cx| { let mut buffer_store = BufferStore::local(worktree_store.clone(), cx); - buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx); + buffer_store.shared(SSH_PROJECT_ID, session.clone(), cx); buffer_store }); @@ -117,7 +116,7 @@ impl HeadlessProject { breakpoint_store.clone(), cx, ); - dap_store.shared(SSH_PROJECT_ID, session.clone().into(), cx); + dap_store.shared(SSH_PROJECT_ID, session.clone(), cx); dap_store }); @@ -129,7 +128,7 @@ impl HeadlessProject { fs.clone(), cx, ); - store.shared(SSH_PROJECT_ID, session.clone().into(), cx); + store.shared(SSH_PROJECT_ID, session.clone(), cx); store }); @@ -152,7 +151,7 @@ impl HeadlessProject { environment.clone(), cx, ); - task_store.shared(SSH_PROJECT_ID, session.clone().into(), cx); + task_store.shared(SSH_PROJECT_ID, session.clone(), cx); task_store }); let settings_observer = cx.new(|cx| { @@ -162,7 +161,7 @@ impl HeadlessProject { task_store.clone(), cx, ); - observer.shared(SSH_PROJECT_ID, session.clone().into(), cx); + observer.shared(SSH_PROJECT_ID, session.clone(), cx); observer }); @@ -183,7 +182,7 @@ impl HeadlessProject { fs.clone(), cx, ); - lsp_store.shared(SSH_PROJECT_ID, session.clone().into(), cx); + lsp_store.shared(SSH_PROJECT_ID, session.clone(), cx); lsp_store }); @@ -194,15 +193,11 @@ impl HeadlessProject { languages.clone(), ); - cx.subscribe( - &buffer_store, - |_this, _buffer_store, event, cx| match event { - BufferStoreEvent::BufferAdded(buffer) => { - cx.subscribe(buffer, Self::on_buffer_event).detach(); - } - _ => {} - }, - ) + cx.subscribe(&buffer_store, |_this, _buffer_store, event, cx| { + if let BufferStoreEvent::BufferAdded(buffer) = event { + cx.subscribe(buffer, Self::on_buffer_event).detach(); + } + }) .detach(); let extensions = HeadlessExtensionStore::new( @@ -214,8 +209,6 @@ impl HeadlessProject { cx, ); - let client: AnyProtoClient = session.clone().into(); - // local_machine -> ssh handlers session.subscribe_to_entity(SSH_PROJECT_ID, &worktree_store); session.subscribe_to_entity(SSH_PROJECT_ID, &buffer_store); @@ -227,44 +220,45 @@ impl HeadlessProject { session.subscribe_to_entity(SSH_PROJECT_ID, &settings_observer); session.subscribe_to_entity(SSH_PROJECT_ID, &git_store); - client.add_request_handler(cx.weak_entity(), Self::handle_list_remote_directory); - client.add_request_handler(cx.weak_entity(), Self::handle_get_path_metadata); - client.add_request_handler(cx.weak_entity(), Self::handle_shutdown_remote_server); - client.add_request_handler(cx.weak_entity(), Self::handle_ping); + session.add_request_handler(cx.weak_entity(), Self::handle_list_remote_directory); + session.add_request_handler(cx.weak_entity(), Self::handle_get_path_metadata); + session.add_request_handler(cx.weak_entity(), Self::handle_shutdown_remote_server); + session.add_request_handler(cx.weak_entity(), Self::handle_ping); - client.add_entity_request_handler(Self::handle_add_worktree); - client.add_request_handler(cx.weak_entity(), Self::handle_remove_worktree); + session.add_entity_request_handler(Self::handle_add_worktree); + session.add_request_handler(cx.weak_entity(), Self::handle_remove_worktree); - client.add_entity_request_handler(Self::handle_open_buffer_by_path); - client.add_entity_request_handler(Self::handle_open_new_buffer); - client.add_entity_request_handler(Self::handle_find_search_candidates); - client.add_entity_request_handler(Self::handle_open_server_settings); + session.add_entity_request_handler(Self::handle_open_buffer_by_path); + session.add_entity_request_handler(Self::handle_open_new_buffer); + session.add_entity_request_handler(Self::handle_find_search_candidates); + session.add_entity_request_handler(Self::handle_open_server_settings); - client.add_entity_request_handler(BufferStore::handle_update_buffer); - client.add_entity_message_handler(BufferStore::handle_close_buffer); + session.add_entity_request_handler(BufferStore::handle_update_buffer); + session.add_entity_message_handler(BufferStore::handle_close_buffer); - client.add_request_handler( + session.add_request_handler( extensions.clone().downgrade(), HeadlessExtensionStore::handle_sync_extensions, ); - client.add_request_handler( + session.add_request_handler( extensions.clone().downgrade(), HeadlessExtensionStore::handle_install_extension, ); - BufferStore::init(&client); - WorktreeStore::init(&client); - SettingsObserver::init(&client); - LspStore::init(&client); - TaskStore::init(Some(&client)); - ToolchainStore::init(&client); - DapStore::init(&client, cx); + BufferStore::init(&session); + WorktreeStore::init(&session); + SettingsObserver::init(&session); + LspStore::init(&session); + TaskStore::init(Some(&session)); + ToolchainStore::init(&session); + DapStore::init(&session, cx); // todo(debugger): Re init breakpoint store when we set it up for collab // BreakpointStore::init(&client); - GitStore::init(&client); + GitStore::init(&session); HeadlessProject { - session: client, + next_entry_id: Default::default(), + session, settings_observer, fs, worktree_store, @@ -272,7 +266,6 @@ impl HeadlessProject { lsp_store, task_store, dap_store, - next_entry_id: Default::default(), languages, extensions, git_store, @@ -285,18 +278,17 @@ impl HeadlessProject { event: &BufferEvent, cx: &mut Context<Self>, ) { - match event { - BufferEvent::Operation { - operation, - is_local: true, - } => cx - .background_spawn(self.session.request(proto::UpdateBuffer { - project_id: SSH_PROJECT_ID, - buffer_id: buffer.read(cx).remote_id().to_proto(), - operations: vec![serialize_operation(operation)], - })) - .detach(), - _ => {} + if let BufferEvent::Operation { + operation, + is_local: true, + } = event + { + cx.background_spawn(self.session.request(proto::UpdateBuffer { + project_id: SSH_PROJECT_ID, + buffer_id: buffer.read(cx).remote_id().to_proto(), + operations: vec![serialize_operation(operation)], + })) + .detach() } } @@ -348,7 +340,7 @@ impl HeadlessProject { .iter() .map(|action| action.title.to_string()) .collect(), - level: Some(prompt_to_proto(&prompt)), + level: Some(prompt_to_proto(prompt)), lsp_name: prompt.lsp_name.clone(), message: prompt.message.clone(), }); @@ -372,7 +364,7 @@ impl HeadlessProject { mut cx: AsyncApp, ) -> Result<proto::AddWorktreeResponse> { use client::ErrorCodeExt; - let fs = this.read_with(&mut cx, |this, _| this.fs.clone())?; + let fs = this.read_with(&cx, |this, _| this.fs.clone())?; let path = PathBuf::from_proto(shellexpand::tilde(&message.payload.path).to_string()); let canonicalized = match fs.canonicalize(&path).await { @@ -388,7 +380,7 @@ impl HeadlessProject { let parent = fs.canonicalize(parent).await.map_err(|_| { anyhow!( proto::ErrorCode::DevServerProjectPathDoesNotExist - .with_tag("path", &path.to_string_lossy().as_ref()) + .with_tag("path", path.to_string_lossy().as_ref()) ) })?; parent.join(path.file_name().unwrap()) @@ -396,7 +388,7 @@ impl HeadlessProject { }; let worktree = this - .read_with(&mut cx.clone(), |this, _| { + .read_with(&cx.clone(), |this, _| { Worktree::local( Arc::from(canonicalized.as_path()), message.payload.visible, @@ -407,7 +399,7 @@ impl HeadlessProject { })? .await?; - let response = this.read_with(&mut cx, |_, cx| { + let response = this.read_with(&cx, |_, cx| { let worktree = worktree.read(cx); proto::AddWorktreeResponse { worktree_id: worktree.id().to_proto(), @@ -586,7 +578,7 @@ impl HeadlessProject { let buffer_store = this.read_with(&cx, |this, _| this.buffer_store.clone())?; while let Ok(buffer) = results.recv().await { - let buffer_id = buffer.read_with(&mut cx, |this, _| this.remote_id())?; + let buffer_id = buffer.read_with(&cx, |this, _| this.remote_id())?; response.buffer_ids.push(buffer_id.to_proto()); buffer_store .update(&mut cx, |buffer_store, cx| { diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index dc7fab8c3c..3352b317cb 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -19,11 +19,11 @@ use project::project_settings::ProjectSettings; use proto::CrashReport; use release_channel::{AppVersion, RELEASE_CHANNEL, ReleaseChannel}; -use remote::proxy::ProxyLaunchError; -use remote::ssh_session::ChannelClient; +use remote::SshRemoteClient; use remote::{ json_log::LogRecord, protocol::{read_message, write_message}, + proxy::ProxyLaunchError, }; use reqwest_client::ReqwestClient; use rpc::proto::{self, Envelope, SSH_PROJECT_ID}; @@ -155,7 +155,7 @@ fn init_panic_hook(session_id: String) { log::error!( "panic occurred: {}\nBacktrace:\n{}", &payload, - (&backtrace).join("\n") + backtrace.join("\n") ); let panic_data = telemetry_events::Panic { @@ -199,8 +199,7 @@ fn init_panic_hook(session_id: String) { })); } -fn handle_crash_files_requests(project: &Entity<HeadlessProject>, client: &Arc<ChannelClient>) { - let client: AnyProtoClient = client.clone().into(); +fn handle_crash_files_requests(project: &Entity<HeadlessProject>, client: &AnyProtoClient) { client.add_request_handler( project.downgrade(), |_, _: TypedEnvelope<proto::GetCrashFiles>, _cx| async move { @@ -276,7 +275,7 @@ fn start_server( listeners: ServerListeners, log_rx: Receiver<Vec<u8>>, cx: &mut App, -) -> Arc<ChannelClient> { +) -> AnyProtoClient { // This is the server idle timeout. If no connection comes in this timeout, the server will shut down. const IDLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10 * 60); @@ -334,7 +333,7 @@ fn start_server( let (mut stdin_msg_tx, mut stdin_msg_rx) = mpsc::unbounded::<Envelope>(); cx.background_spawn(async move { while let Ok(msg) = read_message(&mut stdin_stream, &mut input_buffer).await { - if let Err(_) = stdin_msg_tx.send(msg).await { + if (stdin_msg_tx.send(msg).await).is_err() { break; } } @@ -395,7 +394,7 @@ fn start_server( }) .detach(); - ChannelClient::new(incoming_rx, outgoing_tx, cx, "server") + SshRemoteClient::proto_client_from_channels(incoming_rx, outgoing_tx, cx, "server") } fn init_paths() -> anyhow::Result<()> { @@ -622,7 +621,7 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> { Err(anyhow!(error))?; } n => { - stderr.write_all(&mut stderr_buffer[..n]).await?; + stderr.write_all(&stderr_buffer[..n]).await?; stderr.flush().await?; } } @@ -792,15 +791,12 @@ async fn write_size_prefixed_buffer<S: AsyncWrite + Unpin>( } fn initialize_settings( - session: Arc<ChannelClient>, + session: AnyProtoClient, fs: Arc<dyn Fs>, cx: &mut App, ) -> watch::Receiver<Option<NodeBinaryOptions>> { - let user_settings_file_rx = watch_config_file( - &cx.background_executor(), - fs, - paths::settings_file().clone(), - ); + let user_settings_file_rx = + watch_config_file(cx.background_executor(), fs, paths::settings_file().clone()); handle_settings_file_changes(user_settings_file_rx, cx, { let session = session.clone(); @@ -894,7 +890,8 @@ pub fn handle_settings_file_changes( fn read_proxy_settings(cx: &mut Context<HeadlessProject>) -> Option<Url> { let proxy_str = ProxySettings::get_global(cx).proxy.to_owned(); - let proxy_url = proxy_str + + proxy_str .as_ref() .and_then(|input: &String| { input @@ -902,8 +899,7 @@ fn read_proxy_settings(cx: &mut Context<HeadlessProject>) -> Option<Url> { .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e)) .ok() }) - .or_else(read_proxy_from_env); - proxy_url + .or_else(read_proxy_from_env) } fn daemonize() -> Result<ControlFlow<()>> { @@ -954,13 +950,13 @@ fn cleanup_old_binaries() -> Result<()> { for entry in std::fs::read_dir(server_dir)? { let path = entry?.path(); - if let Some(file_name) = path.file_name() { - if let Some(version) = file_name.to_string_lossy().strip_prefix(&prefix) { - if !is_new_version(version) && !is_file_in_use(file_name) { - log::info!("removing old remote server binary: {:?}", path); - std::fs::remove_file(&path)?; - } - } + if let Some(file_name) = path.file_name() + && let Some(version) = file_name.to_string_lossy().strip_prefix(&prefix) + && !is_new_version(version) + && !is_file_in_use(file_name) + { + log::info!("removing old remote server binary: {:?}", path); + std::fs::remove_file(&path)?; } } diff --git a/crates/repl/src/components/kernel_options.rs b/crates/repl/src/components/kernel_options.rs index cd73783b4c..714cb3aed3 100644 --- a/crates/repl/src/components/kernel_options.rs +++ b/crates/repl/src/components/kernel_options.rs @@ -126,7 +126,7 @@ impl PickerDelegate for KernelPickerDelegate { .collect() }; - return Task::ready(()); + Task::ready(()) } fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) { @@ -269,10 +269,9 @@ where }; let picker_view = cx.new(|cx| { - let picker = Picker::uniform_list(delegate, window, cx) + Picker::uniform_list(delegate, window, cx) .width(rems(30.)) - .max_height(Some(rems(20.).into())); - picker + .max_height(Some(rems(20.).into())) }); PopoverMenu::new("kernel-switcher") diff --git a/crates/repl/src/kernels/native_kernel.rs b/crates/repl/src/kernels/native_kernel.rs index aa6a812809..83271fae16 100644 --- a/crates/repl/src/kernels/native_kernel.rs +++ b/crates/repl/src/kernels/native_kernel.rs @@ -399,10 +399,10 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<LocalKernelS while let Some(path) = kernelspec_dirs.next().await { match path { Ok(path) => { - if fs.is_dir(path.as_path()).await { - if let Ok(kernelspec) = read_kernelspec_at(path, fs).await { - valid_kernelspecs.push(kernelspec); - } + if fs.is_dir(path.as_path()).await + && let Ok(kernelspec) = read_kernelspec_at(path, fs).await + { + valid_kernelspecs.push(kernelspec); } } Err(err) => log::warn!("Error reading kernelspec directory: {err:?}"), @@ -429,14 +429,14 @@ pub async fn local_kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<LocalKer .output() .await; - if let Ok(command) = command { - if command.status.success() { - let python_prefix = String::from_utf8(command.stdout); - if let Ok(python_prefix) = python_prefix { - let python_prefix = PathBuf::from(python_prefix.trim()); - let python_data_dir = python_prefix.join("share").join("jupyter"); - data_dirs.push(python_data_dir); - } + if let Ok(command) = command + && command.status.success() + { + let python_prefix = String::from_utf8(command.stdout); + if let Ok(python_prefix) = python_prefix { + let python_prefix = PathBuf::from(python_prefix.trim()); + let python_data_dir = python_prefix.join("share").join("jupyter"); + data_dirs.push(python_data_dir); } } diff --git a/crates/repl/src/notebook/cell.rs b/crates/repl/src/notebook/cell.rs index 15179a632c..87b8e1d55a 100644 --- a/crates/repl/src/notebook/cell.rs +++ b/crates/repl/src/notebook/cell.rs @@ -91,7 +91,7 @@ fn convert_outputs( cx: &mut App, ) -> Vec<Output> { outputs - .into_iter() + .iter() .map(|output| match output { nbformat::v4::Output::Stream { text, .. } => Output::Stream { content: cx.new(|cx| TerminalOutput::from(&text.0, window, cx)), diff --git a/crates/repl/src/notebook/notebook_ui.rs b/crates/repl/src/notebook/notebook_ui.rs index 36a0af30d0..325d262d9e 100644 --- a/crates/repl/src/notebook/notebook_ui.rs +++ b/crates/repl/src/notebook/notebook_ui.rs @@ -575,7 +575,7 @@ impl project::ProjectItem for NotebookItem { .with_context(|| format!("finding the absolute path of {path:?}"))?; // todo: watch for changes to the file - let file_content = fs.load(&abs_path.as_path()).await?; + let file_content = fs.load(abs_path.as_path()).await?; let notebook = nbformat::parse_notebook(&file_content); let notebook = match notebook { @@ -584,8 +584,8 @@ impl project::ProjectItem for NotebookItem { Ok(nbformat::Notebook::Legacy(legacy_notebook)) => { // TODO: Decide if we want to mutate the notebook by including Cell IDs // and any other conversions - let notebook = nbformat::upgrade_legacy_notebook(legacy_notebook)?; - notebook + + nbformat::upgrade_legacy_notebook(legacy_notebook)? } // Bad notebooks and notebooks v4.0 and below are not supported Err(e) => { diff --git a/crates/repl/src/outputs.rs b/crates/repl/src/outputs.rs index ed252b239f..1508c2b531 100644 --- a/crates/repl/src/outputs.rs +++ b/crates/repl/src/outputs.rs @@ -412,10 +412,10 @@ impl ExecutionView { }; // Check for a clear output marker as the previous output, so we can clear it out - if let Some(output) = self.outputs.last() { - if let Output::ClearOutputWaitMarker = output { - self.outputs.clear(); - } + if let Some(output) = self.outputs.last() + && let Output::ClearOutputWaitMarker = output + { + self.outputs.clear(); } self.outputs.push(output); @@ -433,11 +433,11 @@ impl ExecutionView { let mut any = false; self.outputs.iter_mut().for_each(|output| { - if let Some(other_display_id) = output.display_id().as_ref() { - if other_display_id == display_id { - *output = Output::new(data, Some(display_id.to_owned()), window, cx); - any = true; - } + if let Some(other_display_id) = output.display_id().as_ref() + && other_display_id == display_id + { + *output = Output::new(data, Some(display_id.to_owned()), window, cx); + any = true; } }); @@ -452,19 +452,18 @@ impl ExecutionView { window: &mut Window, cx: &mut Context<Self>, ) -> Option<Output> { - if let Some(last_output) = self.outputs.last_mut() { - if let Output::Stream { + if let Some(last_output) = self.outputs.last_mut() + && let Output::Stream { content: last_stream, } = last_output - { - // Don't need to add a new output, we already have a terminal output - // and can just update the most recent terminal output - last_stream.update(cx, |last_stream, cx| { - last_stream.append_text(text, cx); - cx.notify(); - }); - return None; - } + { + // Don't need to add a new output, we already have a terminal output + // and can just update the most recent terminal output + last_stream.update(cx, |last_stream, cx| { + last_stream.append_text(text, cx); + cx.notify(); + }); + return None; } Some(Output::Stream { diff --git a/crates/repl/src/outputs/plain.rs b/crates/repl/src/outputs/plain.rs index 74c7bfa3c3..ae3c728c8a 100644 --- a/crates/repl/src/outputs/plain.rs +++ b/crates/repl/src/outputs/plain.rs @@ -68,7 +68,7 @@ pub fn text_style(window: &mut Window, cx: &mut App) -> TextStyle { let theme = cx.theme(); - let text_style = TextStyle { + TextStyle { font_family, font_features, font_weight, @@ -81,9 +81,7 @@ pub fn text_style(window: &mut Window, cx: &mut App) -> TextStyle { // These are going to be overridden per-cell color: theme.colors().terminal_foreground, ..Default::default() - }; - - text_style + } } /// Returns the default terminal size for the terminal output. diff --git a/crates/repl/src/repl_editor.rs b/crates/repl/src/repl_editor.rs index 32b59d639d..e97223ceb9 100644 --- a/crates/repl/src/repl_editor.rs +++ b/crates/repl/src/repl_editor.rs @@ -417,10 +417,10 @@ fn runnable_ranges( range: Range<Point>, cx: &mut App, ) -> (Vec<Range<Point>>, Option<Point>) { - if let Some(language) = buffer.language() { - if language.name() == "Markdown".into() { - return (markdown_code_blocks(buffer, range.clone(), cx), None); - } + if let Some(language) = buffer.language() + && language.name() == "Markdown".into() + { + return (markdown_code_blocks(buffer, range.clone(), cx), None); } let (jupytext_snippets, next_cursor) = jupytext_cells(buffer, range.clone()); @@ -434,7 +434,7 @@ fn runnable_ranges( if start_language .zip(end_language) - .map_or(false, |(start, end)| start == end) + .is_some_and(|(start, end)| start == end) { (vec![snippet_range], None) } else { diff --git a/crates/repl/src/repl_sessions_ui.rs b/crates/repl/src/repl_sessions_ui.rs index 2f4c1f86fc..f57dd64770 100644 --- a/crates/repl/src/repl_sessions_ui.rs +++ b/crates/repl/src/repl_sessions_ui.rs @@ -102,21 +102,16 @@ pub fn init(cx: &mut App) { let editor_handle = cx.entity().downgrade(); - if let Some(language) = language { - if language.name() == "Python".into() { - if let (Some(project_path), Some(project)) = (project_path, project) { - let store = ReplStore::global(cx); - store.update(cx, |store, cx| { - store - .refresh_python_kernelspecs( - project_path.worktree_id, - &project, - cx, - ) - .detach_and_log_err(cx); - }); - } - } + if let Some(language) = language + && language.name() == "Python".into() + && let (Some(project_path), Some(project)) = (project_path, project) + { + let store = ReplStore::global(cx); + store.update(cx, |store, cx| { + store + .refresh_python_kernelspecs(project_path.worktree_id, &project, cx) + .detach_and_log_err(cx); + }); } editor diff --git a/crates/repl/src/repl_store.rs b/crates/repl/src/repl_store.rs index 1a3c9fa49a..b9a36a18ae 100644 --- a/crates/repl/src/repl_store.rs +++ b/crates/repl/src/repl_store.rs @@ -171,10 +171,10 @@ impl ReplStore { .map(KernelSpecification::Jupyter) .collect::<Vec<_>>(); - if let Some(remote_task) = remote_kernel_specifications { - if let Ok(remote_specs) = remote_task.await { - all_specs.extend(remote_specs); - } + if let Some(remote_task) = remote_kernel_specifications + && let Ok(remote_specs) = remote_task.await + { + all_specs.extend(remote_specs); } anyhow::Ok(all_specs) diff --git a/crates/reqwest_client/src/reqwest_client.rs b/crates/reqwest_client/src/reqwest_client.rs index 6461a0ae17..9053f4e452 100644 --- a/crates/reqwest_client/src/reqwest_client.rs +++ b/crates/reqwest_client/src/reqwest_client.rs @@ -201,12 +201,11 @@ pub fn poll_read_buf( } fn redact_error(mut error: reqwest::Error) -> reqwest::Error { - if let Some(url) = error.url_mut() { - if let Some(query) = url.query() { - if let Cow::Owned(redacted) = REDACT_REGEX.replace_all(query, "key=REDACTED") { - url.set_query(Some(redacted.as_str())); - } - } + if let Some(url) = error.url_mut() + && let Some(query) = url.query() + && let Cow::Owned(redacted) = REDACT_REGEX.replace_all(query, "key=REDACTED") + { + url.set_query(Some(redacted.as_str())); } error } diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index 575e4318c3..2af9988f03 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -162,10 +162,10 @@ impl RichText { } } for range in &custom_tooltip_ranges { - if range.contains(&idx) { - if let Some(f) = &custom_tooltip_fn { - return f(idx, range.clone(), window, cx); - } + if range.contains(&idx) + && let Some(f) = &custom_tooltip_fn + { + return f(idx, range.clone(), window, cx); } } None @@ -281,13 +281,12 @@ pub fn render_markdown_mut( if style != HighlightStyle::default() && last_run_len < text.len() { let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { - if last_range.end == last_run_len - && last_style == &Highlight::Highlight(style) - { - last_range.end = text.len(); - new_highlight = false; - } + if let Some((last_range, last_style)) = highlights.last_mut() + && last_range.end == last_run_len + && last_style == &Highlight::Highlight(style) + { + last_range.end = text.len(); + new_highlight = false; } if new_highlight { highlights diff --git a/crates/rope/src/chunk.rs b/crates/rope/src/chunk.rs index dc00674380..e3c7d6f750 100644 --- a/crates/rope/src/chunk.rs +++ b/crates/rope/src/chunk.rs @@ -49,7 +49,7 @@ impl Chunk { self.chars_utf16 |= slice.chars_utf16 << base_ix; self.newlines |= slice.newlines << base_ix; self.tabs |= slice.tabs << base_ix; - self.text.push_str(&slice.text); + self.text.push_str(slice.text); } #[inline(always)] @@ -543,7 +543,7 @@ impl Iterator for Tabs { // Since tabs are 1 byte the tab offset is the same as the byte offset let position = TabPosition { byte_offset: tab_offset, - char_offset: char_offset, + char_offset, }; // Remove the tab we've just seen self.tabs ^= 1 << tab_offset; @@ -623,7 +623,7 @@ mod tests { let text = &text[..ix]; log::info!("Chunk: {:?}", text); - let chunk = Chunk::new(&text); + let chunk = Chunk::new(text); verify_chunk(chunk.as_slice(), text); for _ in 0..10 { diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index d8ed3bfac8..0d3f5abbde 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -31,22 +31,21 @@ impl Rope { } pub fn append(&mut self, rope: Rope) { - if let Some(chunk) = rope.chunks.first() { - if self + if let Some(chunk) = rope.chunks.first() + && (self .chunks .last() - .map_or(false, |c| c.text.len() < chunk::MIN_BASE) - || chunk.text.len() < chunk::MIN_BASE - { - self.push_chunk(chunk.as_slice()); + .is_some_and(|c| c.text.len() < chunk::MIN_BASE) + || chunk.text.len() < chunk::MIN_BASE) + { + self.push_chunk(chunk.as_slice()); - let mut chunks = rope.chunks.cursor::<()>(&()); - chunks.next(); - chunks.next(); - self.chunks.append(chunks.suffix(), &()); - self.check_invariants(); - return; - } + let mut chunks = rope.chunks.cursor::<()>(&()); + chunks.next(); + chunks.next(); + self.chunks.append(chunks.suffix(), &()); + self.check_invariants(); + return; } self.chunks.append(rope.chunks.clone(), &()); @@ -735,16 +734,16 @@ impl<'a> Chunks<'a> { self.chunks .search_backward(|summary| summary.text.lines.row > 0); self.offset = *self.chunks.start(); - if let Some(chunk) = self.chunks.item() { - if let Some(newline_ix) = chunk.text.rfind('\n') { - self.offset += newline_ix + 1; - if self.offset_is_valid() { - if self.offset == self.chunks.end() { - self.chunks.next(); - } - - return true; + if let Some(chunk) = self.chunks.item() + && let Some(newline_ix) = chunk.text.rfind('\n') + { + self.offset += newline_ix + 1; + if self.offset_is_valid() { + if self.offset == self.chunks.end() { + self.chunks.next(); } + + return true; } } @@ -817,7 +816,7 @@ impl<'a> Chunks<'a> { } } - return true; + true } } diff --git a/crates/rpc/src/notification.rs b/crates/rpc/src/notification.rs index cb405b63ca..338ef33c8a 100644 --- a/crates/rpc/src/notification.rs +++ b/crates/rpc/src/notification.rs @@ -48,10 +48,10 @@ impl Notification { let Some(Value::String(kind)) = value.remove(KIND) else { unreachable!("kind is the enum tag") }; - if let map::Entry::Occupied(e) = value.entry(ENTITY_ID) { - if e.get().is_u64() { - entity_id = e.remove().as_u64(); - } + if let map::Entry::Occupied(e) = value.entry(ENTITY_ID) + && e.get().is_u64() + { + entity_id = e.remove().as_u64(); } proto::Notification { kind, diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index 80a104641f..98f5fa40e9 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -26,7 +26,6 @@ use std::{ time::Duration, time::Instant, }; -use tracing::instrument; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)] pub struct ConnectionId { @@ -109,7 +108,6 @@ impl Peer { self.epoch.load(SeqCst) } - #[instrument(skip_all)] pub fn add_connection<F, Fut, Out>( self: &Arc<Self>, connection: Connection, @@ -520,10 +518,10 @@ impl Peer { &response.payload { // Remove the transmitting end of the response channel to end the stream. - if let Some(channels) = stream_response_channels.upgrade() { - if let Some(channels) = channels.lock().as_mut() { - channels.remove(&message_id); - } + if let Some(channels) = stream_response_channels.upgrade() + && let Some(channels) = channels.lock().as_mut() + { + channels.remove(&message_id); } None } else { diff --git a/crates/rpc/src/proto_client.rs b/crates/rpc/src/proto_client.rs index eb570b96a3..05b6bd1439 100644 --- a/crates/rpc/src/proto_client.rs +++ b/crates/rpc/src/proto_client.rs @@ -315,4 +315,23 @@ impl AnyProtoClient { }), ); } + + pub fn subscribe_to_entity<E: 'static>(&self, remote_id: u64, entity: &Entity<E>) { + let id = (TypeId::of::<E>(), remote_id); + + let mut message_handlers = self.0.message_handler_set().lock(); + if message_handlers + .entities_by_type_and_remote_id + .contains_key(&id) + { + panic!("already subscribed to entity"); + } + + message_handlers.entities_by_type_and_remote_id.insert( + id, + EntityMessageSubscriber::Entity { + handle: entity.downgrade().into(), + }, + ); + } } diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index ebec96dd7b..bebe4315e4 100644 --- a/crates/rules_library/src/rules_library.rs +++ b/crates/rules_library/src/rules_library.rs @@ -49,7 +49,7 @@ actions!( ] ); -const BUILT_IN_TOOLTIP_TEXT: &'static str = concat!( +const BUILT_IN_TOOLTIP_TEXT: &str = concat!( "This rule supports special functionality.\n", "It's read-only, but you can remove it from your default rules." ); @@ -456,11 +456,11 @@ impl RulesLibrary { pub fn new_rule(&mut self, window: &mut Window, cx: &mut Context<Self>) { // If we already have an untitled rule, use that instead // of creating a new one. - if let Some(metadata) = self.store.read(cx).first() { - if metadata.title.is_none() { - self.load_rule(metadata.id, true, window, cx); - return; - } + if let Some(metadata) = self.store.read(cx).first() + && metadata.title.is_none() + { + self.load_rule(metadata.id, true, window, cx); + return; } let prompt_id = PromptId::new(); @@ -703,18 +703,14 @@ impl RulesLibrary { .delegate .matches .get(picker.delegate.selected_index()) - .map_or(true, |old_selected_prompt| { - old_selected_prompt.id != prompt_id - }) - { - if let Some(ix) = picker + .is_none_or(|old_selected_prompt| old_selected_prompt.id != prompt_id) + && let Some(ix) = picker .delegate .matches .iter() .position(|mat| mat.id == prompt_id) - { - picker.set_selected_index(ix, None, true, window, cx); - } + { + picker.set_selected_index(ix, None, true, window, cx); } } else { picker.focus(window, cx); @@ -869,10 +865,10 @@ impl RulesLibrary { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(rule_id) = self.active_rule_id { - if let Some(rule_editor) = self.rule_editors.get(&rule_id) { - window.focus(&rule_editor.body_editor.focus_handle(cx)); - } + if let Some(rule_id) = self.active_rule_id + && let Some(rule_editor) = self.rule_editors.get(&rule_id) + { + window.focus(&rule_editor.body_editor.focus_handle(cx)); } } @@ -882,10 +878,10 @@ impl RulesLibrary { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(rule_id) = self.active_rule_id { - if let Some(rule_editor) = self.rule_editors.get(&rule_id) { - window.focus(&rule_editor.title_editor.focus_handle(cx)); - } + if let Some(rule_id) = self.active_rule_id + && let Some(rule_editor) = self.rule_editors.get(&rule_id) + { + window.focus(&rule_editor.title_editor.focus_handle(cx)); } } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 189f48e6b6..75042f184f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -794,15 +794,13 @@ impl BufferSearchBar { } pub fn activate_current_match(&mut self, window: &mut Window, cx: &mut Context<Self>) { - if let Some(match_ix) = self.active_match_index { - if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { - if let Some(matches) = self - .searchable_items_with_matches - .get(&active_searchable_item.downgrade()) - { - active_searchable_item.activate_match(match_ix, matches, window, cx) - } - } + if let Some(match_ix) = self.active_match_index + && let Some(active_searchable_item) = self.active_searchable_item.as_ref() + && let Some(matches) = self + .searchable_items_with_matches + .get(&active_searchable_item.downgrade()) + { + active_searchable_item.activate_match(match_ix, matches, window, cx) } } @@ -951,16 +949,15 @@ impl BufferSearchBar { window: &mut Window, cx: &mut Context<Self>, ) { - if !self.dismissed && self.active_match_index.is_some() { - if let Some(searchable_item) = self.active_searchable_item.as_ref() { - if let Some(matches) = self - .searchable_items_with_matches - .get(&searchable_item.downgrade()) - { - searchable_item.select_matches(matches, window, cx); - self.focus_editor(&FocusEditor, window, cx); - } - } + if !self.dismissed + && self.active_match_index.is_some() + && let Some(searchable_item) = self.active_searchable_item.as_ref() + && let Some(matches) = self + .searchable_items_with_matches + .get(&searchable_item.downgrade()) + { + searchable_item.select_matches(matches, window, cx); + self.focus_editor(&FocusEditor, window, cx); } } @@ -971,59 +968,55 @@ impl BufferSearchBar { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(index) = self.active_match_index { - if let Some(searchable_item) = self.active_searchable_item.as_ref() { - if let Some(matches) = self - .searchable_items_with_matches - .get(&searchable_item.downgrade()) - .filter(|matches| !matches.is_empty()) - { - // If 'wrapscan' is disabled, searches do not wrap around the end of the file. - if !EditorSettings::get_global(cx).search_wrap - && ((direction == Direction::Next && index + count >= matches.len()) - || (direction == Direction::Prev && index < count)) - { - crate::show_no_more_matches(window, cx); - return; - } - 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.activate_match(new_match_index, matches, window, cx); - } + if let Some(index) = self.active_match_index + && let Some(searchable_item) = self.active_searchable_item.as_ref() + && let Some(matches) = self + .searchable_items_with_matches + .get(&searchable_item.downgrade()) + .filter(|matches| !matches.is_empty()) + { + // If 'wrapscan' is disabled, searches do not wrap around the end of the file. + if !EditorSettings::get_global(cx).search_wrap + && ((direction == Direction::Next && index + count >= matches.len()) + || (direction == Direction::Prev && index < count)) + { + crate::show_no_more_matches(window, cx); + return; } + 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.activate_match(new_match_index, matches, window, cx); } } pub fn select_first_match(&mut self, window: &mut Window, cx: &mut Context<Self>) { - if let Some(searchable_item) = self.active_searchable_item.as_ref() { - if let Some(matches) = self + if let Some(searchable_item) = self.active_searchable_item.as_ref() + && let Some(matches) = self .searchable_items_with_matches .get(&searchable_item.downgrade()) - { - if matches.is_empty() { - return; - } - searchable_item.update_matches(matches, window, cx); - searchable_item.activate_match(0, matches, window, cx); + { + if matches.is_empty() { + return; } + searchable_item.update_matches(matches, window, cx); + searchable_item.activate_match(0, matches, window, cx); } } pub fn select_last_match(&mut self, window: &mut Window, cx: &mut Context<Self>) { - if let Some(searchable_item) = self.active_searchable_item.as_ref() { - if let Some(matches) = self + if let Some(searchable_item) = self.active_searchable_item.as_ref() + && let Some(matches) = self .searchable_items_with_matches .get(&searchable_item.downgrade()) - { - if matches.is_empty() { - return; - } - let new_match_index = matches.len() - 1; - searchable_item.update_matches(matches, window, cx); - searchable_item.activate_match(new_match_index, matches, window, cx); + { + if matches.is_empty() { + return; } + let new_match_index = matches.len() - 1; + searchable_item.update_matches(matches, window, cx); + searchable_item.activate_match(new_match_index, matches, window, cx); } } @@ -1344,15 +1337,14 @@ impl BufferSearchBar { window: &mut Window, cx: &mut Context<Self>, ) { - if self.query(cx).is_empty() { - if let Some(new_query) = self + if self.query(cx).is_empty() + && let Some(new_query) = self .search_history - .current(&mut self.search_history_cursor) + .current(&self.search_history_cursor) .map(str::to_string) - { - drop(self.search(&new_query, Some(self.search_options), window, cx)); - return; - } + { + drop(self.search(&new_query, Some(self.search_options), window, cx)); + return; } if let Some(new_query) = self @@ -1384,25 +1376,23 @@ impl BufferSearchBar { fn replace_next(&mut self, _: &ReplaceNext, window: &mut Window, cx: &mut Context<Self>) { let mut should_propagate = true; - if !self.dismissed && self.active_search.is_some() { - if let Some(searchable_item) = self.active_searchable_item.as_ref() { - if let Some(query) = self.active_search.as_ref() { - if let Some(matches) = self - .searchable_items_with_matches - .get(&searchable_item.downgrade()) - { - if let Some(active_index) = self.active_match_index { - let query = query - .as_ref() - .clone() - .with_replacement(self.replacement(cx)); - searchable_item.replace(matches.at(active_index), &query, window, cx); - self.select_next_match(&SelectNextMatch, window, cx); - } - should_propagate = false; - } - } + if !self.dismissed + && self.active_search.is_some() + && let Some(searchable_item) = self.active_searchable_item.as_ref() + && let Some(query) = self.active_search.as_ref() + && let Some(matches) = self + .searchable_items_with_matches + .get(&searchable_item.downgrade()) + { + if let Some(active_index) = self.active_match_index { + let query = query + .as_ref() + .clone() + .with_replacement(self.replacement(cx)); + searchable_item.replace(matches.at(active_index), &query, window, cx); + self.select_next_match(&SelectNextMatch, window, cx); } + should_propagate = false; } if !should_propagate { cx.stop_propagation(); @@ -1410,21 +1400,19 @@ impl BufferSearchBar { } pub fn replace_all(&mut self, _: &ReplaceAll, window: &mut Window, cx: &mut Context<Self>) { - if !self.dismissed && self.active_search.is_some() { - if let Some(searchable_item) = self.active_searchable_item.as_ref() { - if let Some(query) = self.active_search.as_ref() { - if let Some(matches) = self - .searchable_items_with_matches - .get(&searchable_item.downgrade()) - { - let query = query - .as_ref() - .clone() - .with_replacement(self.replacement(cx)); - searchable_item.replace_all(&mut matches.iter(), &query, window, cx); - } - } - } + if !self.dismissed + && self.active_search.is_some() + && let Some(searchable_item) = self.active_searchable_item.as_ref() + && let Some(query) = self.active_search.as_ref() + && let Some(matches) = self + .searchable_items_with_matches + .get(&searchable_item.downgrade()) + { + let query = query + .as_ref() + .clone() + .with_replacement(self.replacement(cx)); + searchable_item.replace_all(&mut matches.iter(), &query, window, cx); } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 056c3556ba..0886654d62 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -775,15 +775,15 @@ impl ProjectSearchView { // Subscribe to query_editor in order to reraise editor events for workspace item activation purposes subscriptions.push( cx.subscribe(&query_editor, |this, _, event: &EditorEvent, cx| { - if let EditorEvent::Edited { .. } = event { - if EditorSettings::get_global(cx).use_smartcase_search { - let query = this.search_query_text(cx); - if !query.is_empty() - && this.search_options.contains(SearchOptions::CASE_SENSITIVE) - != contains_uppercase(&query) - { - this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); - } + if let EditorEvent::Edited { .. } = event + && EditorSettings::get_global(cx).use_smartcase_search + { + let query = this.search_query_text(cx); + if !query.is_empty() + && this.search_options.contains(SearchOptions::CASE_SENSITIVE) + != contains_uppercase(&query) + { + this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); } } cx.emit(ViewEvent::EditorEvent(event.clone())) @@ -947,14 +947,14 @@ impl ProjectSearchView { { let new_query = search_view.update(cx, |search_view, cx| { let new_query = search_view.build_search_query(cx); - if new_query.is_some() { - if let Some(old_query) = search_view.entity.read(cx).active_query.clone() { - search_view.query_editor.update(cx, |editor, cx| { - editor.set_text(old_query.as_str(), window, cx); - }); - search_view.search_options = SearchOptions::from_query(&old_query); - search_view.adjust_query_regex_language(cx); - } + if new_query.is_some() + && let Some(old_query) = search_view.entity.read(cx).active_query.clone() + { + search_view.query_editor.update(cx, |editor, cx| { + editor.set_text(old_query.as_str(), window, cx); + }); + search_view.search_options = SearchOptions::from_query(&old_query); + search_view.adjust_query_regex_language(cx); } new_query }); @@ -1113,8 +1113,8 @@ impl ProjectSearchView { .await .log_err(); } - let should_search = result != 2; - should_search + + result != 2 } else { true }; @@ -1844,8 +1844,8 @@ impl ProjectSearchBar { ), ] { if editor.focus_handle(cx).is_focused(window) { - if editor.read(cx).text(cx).is_empty() { - if let Some(new_query) = search_view + if editor.read(cx).text(cx).is_empty() + && let Some(new_query) = search_view .entity .read(cx) .project @@ -1853,10 +1853,9 @@ impl ProjectSearchBar { .search_history(kind) .current(search_view.entity.read(cx).cursor(kind)) .map(str::to_string) - { - search_view.set_search_editor(kind, &new_query, window, cx); - return; - } + { + search_view.set_search_editor(kind, &new_query, window, cx); + return; } if let Some(new_query) = search_view.entity.update(cx, |model, cx| { @@ -1925,13 +1924,15 @@ impl Render for ProjectSearchBar { let limit_reached = project_search.limit_reached; let color_override = match ( + &project_search.pending_search, project_search.no_results, &project_search.active_query, &project_search.last_search_query_text, ) { - (Some(true), Some(q), Some(p)) if q.as_str() == p => Some(Color::Error), + (None, Some(true), Some(q), Some(p)) if q.as_str() == p => Some(Color::Error), _ => None, }; + let match_text = search .active_match_index .and_then(|index| { @@ -4110,7 +4111,7 @@ pub mod tests { }); cx.run_until_parked(); let project_search_view = pane - .read_with(&mut cx, |pane, _| { + .read_with(&cx, |pane, _| { pane.active_item() .and_then(|item| item.downcast::<ProjectSearchView>()) }) diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 904c74d03c..1afbc2c23b 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -142,7 +142,7 @@ impl SearchOption { SearchSource::Buffer => { let focus_handle = focus_handle.clone(); button.on_click(move |_: &ClickEvent, window, cx| { - if !focus_handle.is_focused(&window) { + if !focus_handle.is_focused(window) { window.focus(&focus_handle); } window.dispatch_action(action.boxed_clone(), cx); diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 8cc838a8a6..44f6b3fdd2 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -26,7 +26,7 @@ pub(super) fn render_action_button( .on_click({ let focus_handle = focus_handle.clone(); move |_, window, cx| { - if !focus_handle.is_focused(&window) { + if !focus_handle.is_focused(window) { window.focus(&focus_handle); } window.dispatch_action(action.boxed_clone(), cx) diff --git a/crates/semantic_index/src/embedding_index.rs b/crates/semantic_index/src/embedding_index.rs index d2d10ad0ad..eeb3c91fcd 100644 --- a/crates/semantic_index/src/embedding_index.rs +++ b/crates/semantic_index/src/embedding_index.rs @@ -194,11 +194,11 @@ impl EmbeddingIndex { project::PathChange::Added | project::PathChange::Updated | project::PathChange::AddedOrUpdated => { - if let Some(entry) = worktree.entry_for_id(*entry_id) { - if entry.is_file() { - let handle = entries_being_indexed.insert(entry.id); - updated_entries_tx.send((entry.clone(), handle)).await?; - } + if let Some(entry) = worktree.entry_for_id(*entry_id) + && entry.is_file() + { + let handle = entries_being_indexed.insert(entry.id); + updated_entries_tx.send((entry.clone(), handle)).await?; } } project::PathChange::Removed => { diff --git a/crates/semantic_index/src/project_index.rs b/crates/semantic_index/src/project_index.rs index 5e852327dd..60b2770dd3 100644 --- a/crates/semantic_index/src/project_index.rs +++ b/crates/semantic_index/src/project_index.rs @@ -384,10 +384,10 @@ impl ProjectIndex { cx: &App, ) -> Option<Entity<WorktreeIndex>> { for index in self.worktree_indices.values() { - if let WorktreeIndexHandle::Loaded { index, .. } = index { - if index.read(cx).worktree().read(cx).id() == worktree_id { - return Some(index.clone()); - } + if let WorktreeIndexHandle::Loaded { index, .. } = index + && index.read(cx).worktree().read(cx).id() == worktree_id + { + return Some(index.clone()); } } None diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index a9cc08382b..1dafeb072f 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -174,14 +174,13 @@ impl SemanticDb { file_content[start_line_byte_offset..end_line_byte_offset].to_string(); LineEnding::normalize(&mut excerpt_content); - if let Some(prev_result) = loaded_results.last_mut() { - if prev_result.full_path == full_path { - if *prev_result.row_range.end() + 1 == start_row { - prev_result.row_range = *prev_result.row_range.start()..=end_row; - prev_result.excerpt_content.push_str(&excerpt_content); - continue; - } - } + if let Some(prev_result) = loaded_results.last_mut() + && prev_result.full_path == full_path + && *prev_result.row_range.end() + 1 == start_row + { + prev_result.row_range = *prev_result.row_range.start()..=end_row; + prev_result.excerpt_content.push_str(&excerpt_content); + continue; } loaded_results.push(LoadedSearchResult { diff --git a/crates/semantic_index/src/summary_index.rs b/crates/semantic_index/src/summary_index.rs index 6e3aae1344..d1c9a3abac 100644 --- a/crates/semantic_index/src/summary_index.rs +++ b/crates/semantic_index/src/summary_index.rs @@ -324,7 +324,7 @@ impl SummaryIndex { ) -> Vec<(Arc<Path>, Option<MTime>)> { let entry_db_key = db_key_for_path(&entry.path); - match digest_db.get(&txn, &entry_db_key) { + match digest_db.get(txn, &entry_db_key) { Ok(opt_saved_digest) => { // The file path is the same, but the mtime is different. (Or there was no mtime.) // It needs updating, so add it to the backlog! Then, if the backlog is full, drain it and summarize its contents. @@ -379,18 +379,14 @@ impl SummaryIndex { | project::PathChange::Added | project::PathChange::Updated | project::PathChange::AddedOrUpdated => { - if let Some(entry) = worktree.entry_for_id(*entry_id) { - if entry.is_file() { - let needs_summary = Self::add_to_backlog( - Arc::clone(&backlog), - digest_db, - &txn, - entry, - ); + if let Some(entry) = worktree.entry_for_id(*entry_id) + && entry.is_file() + { + let needs_summary = + Self::add_to_backlog(Arc::clone(&backlog), digest_db, &txn, entry); - if !needs_summary.is_empty() { - tx.send(needs_summary).await?; - } + if !needs_summary.is_empty() { + tx.send(needs_summary).await?; } } } @@ -575,7 +571,7 @@ impl SummaryIndex { let code_len = code.len(); cx.spawn(async move |cx| { - let stream = model.stream_completion(request, &cx); + let stream = model.stream_completion(request, cx); cx.background_spawn(async move { let answer: String = stream .await? diff --git a/crates/session/src/session.rs b/crates/session/src/session.rs index f027df8762..438059fef7 100644 --- a/crates/session/src/session.rs +++ b/crates/session/src/session.rs @@ -70,11 +70,11 @@ impl AppSession { let _serialization_task = cx.spawn(async move |_, cx| { let mut current_window_stack = Vec::new(); loop { - if let Some(windows) = cx.update(|cx| window_stack(cx)).ok().flatten() { - if windows != current_window_stack { - store_window_stack(&windows).await; - current_window_stack = windows; - } + if let Some(windows) = cx.update(|cx| window_stack(cx)).ok().flatten() + && windows != current_window_stack + { + store_window_stack(&windows).await; + current_window_stack = windows; } cx.background_executor() diff --git a/crates/settings/src/key_equivalents.rs b/crates/settings/src/key_equivalents.rs index bf08de97ae..6580137535 100644 --- a/crates/settings/src/key_equivalents.rs +++ b/crates/settings/src/key_equivalents.rs @@ -1415,7 +1415,7 @@ pub fn get_key_equivalents(layout: &str) -> Option<HashMap<char, char>> { _ => return None, }; - Some(HashMap::from_iter(mappings.into_iter().cloned())) + Some(HashMap::from_iter(mappings.iter().cloned())) } #[cfg(not(target_os = "macos"))] diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index fb03662290..ae3f42853a 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -358,11 +358,11 @@ impl KeymapFile { let action_input = items[1].clone(); let action_input_string = action_input.to_string(); ( - cx.build_action(&name, Some(action_input)), + cx.build_action(name, Some(action_input)), Some(action_input_string), ) } - Value::String(name) => (cx.build_action(&name, None), None), + Value::String(name) => (cx.build_action(name, None), None), Value::Null => (Ok(NoAction.boxed_clone()), None), _ => { return Err(format!( @@ -543,27 +543,27 @@ impl KeymapFile { // // When a struct with no deserializable fields is added by deriving `Action`, an empty // object schema is produced. The action should be invoked without data in this case. - if let Some(schema) = action_schema { - if schema != empty_object { - let mut matches_action_name = json_schema!({ - "const": name - }); - if let Some(desc) = description.clone() { - add_description(&mut matches_action_name, desc); - } - if let Some(message) = deprecation_messages.get(name) { - add_deprecation(&mut matches_action_name, message.to_string()); - } else if let Some(new_name) = deprecation { - add_deprecation_preferred_name(&mut matches_action_name, new_name); - } - let action_with_input = json_schema!({ - "type": "array", - "items": [matches_action_name, schema], - "minItems": 2, - "maxItems": 2 - }); - keymap_action_alternatives.push(action_with_input); + if let Some(schema) = action_schema + && schema != empty_object + { + let mut matches_action_name = json_schema!({ + "const": name + }); + if let Some(desc) = description.clone() { + add_description(&mut matches_action_name, desc); } + if let Some(message) = deprecation_messages.get(name) { + add_deprecation(&mut matches_action_name, message.to_string()); + } else if let Some(new_name) = deprecation { + add_deprecation_preferred_name(&mut matches_action_name, new_name); + } + let action_with_input = json_schema!({ + "type": "array", + "items": [matches_action_name, schema], + "minItems": 2, + "maxItems": 2 + }); + keymap_action_alternatives.push(action_with_input); } } @@ -593,10 +593,10 @@ impl KeymapFile { match fs.load(paths::keymap_file()).await { result @ Ok(_) => result, Err(err) => { - if let Some(e) = err.downcast_ref::<std::io::Error>() { - if e.kind() == std::io::ErrorKind::NotFound { - return Ok(crate::initial_keymap_content().to_string()); - } + if let Some(e) = err.downcast_ref::<std::io::Error>() + && e.kind() == std::io::ErrorKind::NotFound + { + return Ok(crate::initial_keymap_content().to_string()); } Err(err) } @@ -653,7 +653,7 @@ impl KeymapFile { let is_only_binding = keymap.0[index] .bindings .as_ref() - .map_or(true, |bindings| bindings.len() == 1); + .is_none_or(|bindings| bindings.len() == 1); let key_path: &[&str] = if is_only_binding { &[] } else { @@ -703,7 +703,7 @@ impl KeymapFile { } else if keymap.0[index] .bindings .as_ref() - .map_or(true, |bindings| bindings.len() == 1) + .is_none_or(|bindings| bindings.len() == 1) { // if we are replacing the only binding in the section, // just update the section in place, updating the context @@ -839,7 +839,7 @@ impl KeymapFile { if &action.0 != target_action_value { continue; } - return Some((index, &keystrokes_str)); + return Some((index, keystrokes_str)); } } None @@ -1056,10 +1056,10 @@ mod tests { #[track_caller] fn parse_keystrokes(keystrokes: &str) -> Vec<Keystroke> { - return keystrokes + keystrokes .split(' ') .map(|s| Keystroke::parse(s).expect("Keystrokes valid")) - .collect(); + .collect() } #[test] diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index c43f3e79e8..d31dd82da4 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -67,10 +67,10 @@ pub fn watch_config_file( break; } - if let Ok(contents) = fs.load(&path).await { - if tx.unbounded_send(contents).is_err() { - break; - } + if let Ok(contents) = fs.load(&path).await + && tx.unbounded_send(contents).is_err() + { + break; } } }) @@ -88,12 +88,11 @@ pub fn watch_config_dir( executor .spawn(async move { for file_path in &config_paths { - if fs.metadata(file_path).await.is_ok_and(|v| v.is_some()) { - if let Ok(contents) = fs.load(file_path).await { - if tx.unbounded_send(contents).is_err() { - return; - } - } + if fs.metadata(file_path).await.is_ok_and(|v| v.is_some()) + && let Ok(contents) = fs.load(file_path).await + && tx.unbounded_send(contents).is_err() + { + return; } } @@ -110,10 +109,10 @@ pub fn watch_config_dir( } } Some(PathEventKind::Created) | Some(PathEventKind::Changed) => { - if let Ok(contents) = fs.load(&event.path).await { - if tx.unbounded_send(contents).is_err() { - return; - } + if let Ok(contents) = fs.load(&event.path).await + && tx.unbounded_send(contents).is_err() + { + return; } } _ => {} diff --git a/crates/settings/src/settings_json.rs b/crates/settings/src/settings_json.rs index e6683857e7..a472c50e6c 100644 --- a/crates/settings/src/settings_json.rs +++ b/crates/settings/src/settings_json.rs @@ -72,7 +72,7 @@ pub fn update_value_in_json_text<'a>( } } else if key_path .last() - .map_or(false, |key| preserved_keys.contains(key)) + .is_some_and(|key| preserved_keys.contains(key)) || old_value != new_value { let mut new_value = new_value.clone(); @@ -295,9 +295,9 @@ fn replace_value_in_json_text( } } -const TS_DOCUMENT_KIND: &'static str = "document"; -const TS_ARRAY_KIND: &'static str = "array"; -const TS_COMMENT_KIND: &'static str = "comment"; +const TS_DOCUMENT_KIND: &str = "document"; +const TS_ARRAY_KIND: &str = "array"; +const TS_COMMENT_KIND: &str = "comment"; pub fn replace_top_level_array_value_in_json_text( text: &str, @@ -369,13 +369,12 @@ pub fn replace_top_level_array_value_in_json_text( if cursor.node().kind() == "," { remove_range.end = cursor.node().range().end_byte; } - if let Some(next_newline) = &text[remove_range.end + 1..].find('\n') { - if text[remove_range.end + 1..remove_range.end + next_newline] + if let Some(next_newline) = &text[remove_range.end + 1..].find('\n') + && text[remove_range.end + 1..remove_range.end + next_newline] .chars() .all(|c| c.is_ascii_whitespace()) - { - remove_range.end = remove_range.end + next_newline; - } + { + remove_range.end = remove_range.end + next_newline; } } else { while cursor.goto_previous_sibling() @@ -385,7 +384,7 @@ pub fn replace_top_level_array_value_in_json_text( remove_range.start = cursor.node().range().start_byte; } } - return Ok((remove_range, String::new())); + Ok((remove_range, String::new())) } else { let (mut replace_range, mut replace_value) = replace_value_in_json_text(value_str, key_path, tab_size, new_value, replace_key); @@ -406,7 +405,7 @@ pub fn replace_top_level_array_value_in_json_text( } } - return Ok((replace_range, replace_value)); + Ok((replace_range, replace_value)) } } @@ -508,10 +507,10 @@ pub fn append_top_level_array_value_in_json_text( replace_value.insert(0, ','); } } else { - if let Some(prev_newline) = text[..replace_range.start].rfind('\n') { - if text[prev_newline..replace_range.start].trim().is_empty() { - replace_range.start = prev_newline; - } + if let Some(prev_newline) = text[..replace_range.start].rfind('\n') + && text[prev_newline..replace_range.start].trim().is_empty() + { + replace_range.start = prev_newline; } let indent = format!("\n{space:width$}", width = tab_size); replace_value = replace_value.replace('\n', &indent); @@ -528,7 +527,7 @@ pub fn append_top_level_array_value_in_json_text( let descendant_index = cursor.descendant_index(); let res = cursor.goto_first_child() && cursor.node().kind() == kind; cursor.goto_descendant(descendant_index); - return res; + res } } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index bfdafbffe8..211db46c6c 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -346,14 +346,13 @@ impl SettingsStore { } let mut profile_value = None; - if let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>() { - if let Some(profiles) = self.raw_user_settings.get("profiles") { - if let Some(profile_settings) = profiles.get(&active_profile.0) { - profile_value = setting_value - .deserialize_setting(profile_settings) - .log_err(); - } - } + if let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>() + && let Some(profiles) = self.raw_user_settings.get("profiles") + && let Some(profile_settings) = profiles.get(&active_profile.0) + { + profile_value = setting_value + .deserialize_setting(profile_settings) + .log_err(); } let server_value = self @@ -482,10 +481,10 @@ impl SettingsStore { match fs.load(paths::settings_file()).await { result @ Ok(_) => result, Err(err) => { - if let Some(e) = err.downcast_ref::<std::io::Error>() { - if e.kind() == std::io::ErrorKind::NotFound { - return Ok(crate::initial_user_settings_content().to_string()); - } + if let Some(e) = err.downcast_ref::<std::io::Error>() + && e.kind() == std::io::ErrorKind::NotFound + { + return Ok(crate::initial_user_settings_content().to_string()); } Err(err) } @@ -496,10 +495,10 @@ impl SettingsStore { match fs.load(paths::global_settings_file()).await { result @ Ok(_) => result, Err(err) => { - if let Some(e) = err.downcast_ref::<std::io::Error>() { - if e.kind() == std::io::ErrorKind::NotFound { - return Ok("{}".to_string()); - } + if let Some(e) = err.downcast_ref::<std::io::Error>() + && e.kind() == std::io::ErrorKind::NotFound + { + return Ok("{}".to_string()); } Err(err) } @@ -955,13 +954,13 @@ impl SettingsStore { let mut setting_schema = setting_value.json_schema(&mut generator); if let Some(key) = setting_value.key() { - if let Some(properties) = combined_schema.get_mut("properties") { - if let Some(properties_obj) = properties.as_object_mut() { - if let Some(target) = properties_obj.get_mut(key) { - merge_schema(target, setting_schema.to_value()); - } else { - properties_obj.insert(key.to_string(), setting_schema.to_value()); - } + if let Some(properties) = combined_schema.get_mut("properties") + && let Some(properties_obj) = properties.as_object_mut() + { + if let Some(target) = properties_obj.get_mut(key) { + merge_schema(target, setting_schema.to_value()); + } else { + properties_obj.insert(key.to_string(), setting_schema.to_value()); } } } else { @@ -1038,16 +1037,15 @@ impl SettingsStore { | "additionalProperties" => { if let Some(old_value) = target_obj.insert(source_key.clone(), source_value.clone()) + && old_value != source_value { - if old_value != source_value { - log::error!( - "bug: while merging JSON schemas, \ + log::error!( + "bug: while merging JSON schemas, \ mismatch `\"{}\": {}` (before was `{}`)", - source_key, - old_value, - source_value - ); - } + source_key, + old_value, + source_value + ); } } _ => { @@ -1168,35 +1166,31 @@ impl SettingsStore { if let Some(release_settings) = &self .raw_user_settings .get(release_channel::RELEASE_CHANNEL.dev_name()) - { - if let Some(release_settings) = setting_value + && let Some(release_settings) = setting_value .deserialize_setting(release_settings) .log_err() - { - release_channel_settings = Some(release_settings); - } + { + release_channel_settings = Some(release_settings); } let mut os_settings = None; - if let Some(settings) = &self.raw_user_settings.get(env::consts::OS) { - if let Some(settings) = setting_value.deserialize_setting(settings).log_err() { - os_settings = Some(settings); - } + if let Some(settings) = &self.raw_user_settings.get(env::consts::OS) + && let Some(settings) = setting_value.deserialize_setting(settings).log_err() + { + os_settings = Some(settings); } let mut profile_settings = None; - if let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>() { - if let Some(profiles) = self.raw_user_settings.get("profiles") { - if let Some(profile_json) = profiles.get(&active_profile.0) { - profile_settings = - setting_value.deserialize_setting(profile_json).log_err(); - } - } + if let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>() + && let Some(profiles) = self.raw_user_settings.get("profiles") + && let Some(profile_json) = profiles.get(&active_profile.0) + { + profile_settings = setting_value.deserialize_setting(profile_json).log_err(); } // If the global settings file changed, reload the global value for the field. - if changed_local_path.is_none() { - if let Some(value) = setting_value + if changed_local_path.is_none() + && let Some(value) = setting_value .load_setting( SettingsSources { default: &default_settings, @@ -1212,9 +1206,8 @@ impl SettingsStore { cx, ) .log_err() - { - setting_value.set_global_value(value); - } + { + setting_value.set_global_value(value); } // Reload the local values for the setting. @@ -1223,12 +1216,12 @@ impl SettingsStore { for ((root_id, directory_path), local_settings) in &self.raw_local_settings { // Build a stack of all of the local values for that setting. while let Some(prev_entry) = paths_stack.last() { - if let Some((prev_root_id, prev_path)) = prev_entry { - if root_id != prev_root_id || !directory_path.starts_with(prev_path) { - paths_stack.pop(); - project_settings_stack.pop(); - continue; - } + if let Some((prev_root_id, prev_path)) = prev_entry + && (root_id != prev_root_id || !directory_path.starts_with(prev_path)) + { + paths_stack.pop(); + project_settings_stack.pop(); + continue; } break; } @@ -1240,8 +1233,7 @@ impl SettingsStore { // If a local settings file changed, then avoid recomputing local // settings for any path outside of that directory. - if changed_local_path.map_or( - false, + if changed_local_path.is_some_and( |(changed_root_id, changed_local_path)| { *root_id != changed_root_id || !directory_path.starts_with(changed_local_path) diff --git a/crates/settings_profile_selector/src/settings_profile_selector.rs b/crates/settings_profile_selector/src/settings_profile_selector.rs index 8a34c12051..25be67bfd7 100644 --- a/crates/settings_profile_selector/src/settings_profile_selector.rs +++ b/crates/settings_profile_selector/src/settings_profile_selector.rs @@ -126,7 +126,7 @@ impl SettingsProfileSelectorDelegate { ) -> Option<String> { let mat = self.matches.get(self.selected_index)?; let profile_name = self.profile_names.get(mat.candidate_id)?; - return Self::update_active_profile_name_global(profile_name.clone(), cx); + Self::update_active_profile_name_global(profile_name.clone(), cx) } fn update_active_profile_name_global( diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index b65acbe616..b1e9698044 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -270,7 +270,7 @@ impl ConflictState { for origin in indices.iter() { conflicts[origin.index] = - origin.get_conflict_with(if origin == fst { &snd } else { &fst }) + origin.get_conflict_with(if origin == fst { snd } else { fst }) } has_user_conflicts |= fst.override_source == KeybindSource::User @@ -472,13 +472,7 @@ impl KeymapEditor { fn current_keystroke_query(&self, cx: &App) -> Vec<Keystroke> { match self.search_mode { - SearchMode::KeyStroke { .. } => self - .keystroke_editor - .read(cx) - .keystrokes() - .iter() - .cloned() - .collect(), + SearchMode::KeyStroke { .. } => self.keystroke_editor.read(cx).keystrokes().to_vec(), SearchMode::Normal => Default::default(), } } @@ -559,7 +553,7 @@ impl KeymapEditor { if exact_match { keystrokes_match_exactly(&keystroke_query, keystrokes) } else if keystroke_query.len() > keystrokes.len() { - return false; + false } else { for keystroke_offset in 0..keystrokes.len() { let mut found_count = 0; @@ -574,12 +568,9 @@ impl KeymapEditor { query.modifiers.is_subset_of(&keystroke.modifiers) && ((query.key.is_empty() || query.key == keystroke.key) - && query - .key_char - .as_ref() - .map_or(true, |q_kc| { - q_kc == &keystroke.key - })); + && query.key_char.as_ref().is_none_or( + |q_kc| q_kc == &keystroke.key, + )); if matches { found_count += 1; query_cursor += 1; @@ -591,7 +582,7 @@ impl KeymapEditor { return true; } } - return false; + false } }) }); @@ -630,8 +621,7 @@ impl KeymapEditor { let key_bindings_ptr = cx.key_bindings(); let lock = key_bindings_ptr.borrow(); let key_bindings = lock.bindings(); - let mut unmapped_action_names = - HashSet::from_iter(cx.all_action_names().into_iter().copied()); + let mut unmapped_action_names = HashSet::from_iter(cx.all_action_names().iter().copied()); let action_documentation = cx.action_documentation(); let mut generator = KeymapFile::action_schema_generator(); let actions_with_schemas = HashSet::from_iter( @@ -673,8 +663,8 @@ impl KeymapEditor { action_name, action_arguments, &actions_with_schemas, - &action_documentation, - &humanized_action_names, + action_documentation, + humanized_action_names, ); let index = processed_bindings.len(); @@ -696,8 +686,8 @@ impl KeymapEditor { action_name, None, &actions_with_schemas, - &action_documentation, - &humanized_action_names, + action_documentation, + humanized_action_names, ); let string_match_candidate = StringMatchCandidate::new(index, &action_information.humanized_name); @@ -1294,7 +1284,7 @@ struct HumanizedActionNameCache { impl HumanizedActionNameCache { fn new(cx: &App) -> Self { - let cache = HashMap::from_iter(cx.all_action_names().into_iter().map(|&action_name| { + let cache = HashMap::from_iter(cx.all_action_names().iter().map(|&action_name| { ( action_name, command_palette::humanize_action_name(action_name).into(), @@ -1862,18 +1852,15 @@ impl Render for KeymapEditor { mouse_down_event: &gpui::MouseDownEvent, window, cx| { - match mouse_down_event.button { - MouseButton::Right => { - this.select_index( - row_index, None, window, cx, - ); - this.create_context_menu( - mouse_down_event.position, - window, - cx, - ); - } - _ => {} + if mouse_down_event.button == MouseButton::Right { + this.select_index( + row_index, None, window, cx, + ); + this.create_context_menu( + mouse_down_event.position, + window, + cx, + ); } }, )) @@ -2017,21 +2004,21 @@ impl RenderOnce for SyntaxHighlightedText { #[derive(PartialEq)] struct InputError { - severity: ui::Severity, + severity: Severity, content: SharedString, } impl InputError { fn warning(message: impl Into<SharedString>) -> Self { Self { - severity: ui::Severity::Warning, + severity: Severity::Warning, content: message.into(), } } fn error(message: anyhow::Error) -> Self { Self { - severity: ui::Severity::Error, + severity: Severity::Error, content: message.to_string().into(), } } @@ -2158,9 +2145,11 @@ impl KeybindingEditorModal { } fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool { - if self.error.as_ref().is_some_and(|old_error| { - old_error.severity == ui::Severity::Warning && *old_error == error - }) { + if self + .error + .as_ref() + .is_some_and(|old_error| old_error.severity == Severity::Warning && *old_error == error) + { false } else { self.error = Some(error); @@ -2183,7 +2172,7 @@ impl KeybindingEditorModal { }) .transpose()?; - cx.build_action(&self.editing_keybind.action().name, value) + cx.build_action(self.editing_keybind.action().name, value) .context("Failed to validate action arguments")?; Ok(action_arguments) } @@ -2712,7 +2701,7 @@ impl ActionArgumentsEditor { }) .ok(); } - return result; + result }) .detach_and_log_err(cx); Self { @@ -2815,7 +2804,7 @@ impl Render for ActionArgumentsEditor { self.editor .update(cx, |editor, _| editor.set_text_style_refinement(text_style)); - return v_flex().w_full().child( + v_flex().w_full().child( h_flex() .min_h_8() .min_w_48() @@ -2828,7 +2817,7 @@ impl Render for ActionArgumentsEditor { .border_color(border_color) .track_focus(&self.focus_handle) .child(self.editor.clone()), - ); + ) } } @@ -2855,11 +2844,8 @@ impl CompletionProvider for KeyContextCompletionProvider { break; } } - let start_anchor = buffer.anchor_before( - buffer_position - .to_offset(&buffer) - .saturating_sub(count_back), - ); + let start_anchor = + buffer.anchor_before(buffer_position.to_offset(buffer).saturating_sub(count_back)); let replace_range = start_anchor..buffer_position; gpui::Task::ready(Ok(vec![project::CompletionResponse { completions: self @@ -2889,9 +2875,9 @@ impl CompletionProvider for KeyContextCompletionProvider { _menu_is_open: bool, _cx: &mut Context<Editor>, ) -> bool { - text.chars().last().map_or(false, |last_char| { - last_char.is_ascii_alphanumeric() || last_char == '_' - }) + text.chars() + .last() + .is_some_and(|last_char| last_char.is_ascii_alphanumeric() || last_char == '_') } } @@ -2910,7 +2896,7 @@ async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) Some(task) => task.await.context("Failed to load JSON language").log_err(), None => None, }; - return json_language.unwrap_or_else(|| { + json_language.unwrap_or_else(|| { Arc::new(Language::new( LanguageConfig { name: "JSON".into(), @@ -2918,7 +2904,7 @@ async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) }, Some(tree_sitter_json::LANGUAGE.into()), )) - }); + }) } async fn load_keybind_context_language( @@ -2942,7 +2928,7 @@ async fn load_keybind_context_language( .log_err(), None => None, }; - return language.unwrap_or_else(|| { + language.unwrap_or_else(|| { Arc::new(Language::new( LanguageConfig { name: "Zed Keybind Context".into(), @@ -2950,7 +2936,7 @@ async fn load_keybind_context_language( }, Some(tree_sitter_rust::LANGUAGE.into()), )) - }); + }) } async fn save_keybinding_update( @@ -2976,14 +2962,14 @@ async fn save_keybinding_update( let target = settings::KeybindUpdateTarget { context: existing_context, keystrokes: existing_keystrokes, - action_name: &existing.action().name, + action_name: existing.action().name, action_arguments: existing_args, }; let source = settings::KeybindUpdateTarget { context: action_mapping.context.as_ref().map(|a| &***a), keystrokes: &action_mapping.keystrokes, - action_name: &existing.action().name, + action_name: existing.action().name, action_arguments: new_args, }; @@ -3037,7 +3023,7 @@ async fn remove_keybinding( target: settings::KeybindUpdateTarget { context: existing.context().and_then(KeybindContextString::local_str), keystrokes, - action_name: &existing.action().name, + action_name: existing.action().name, action_arguments: existing .action() .arguments @@ -3130,7 +3116,7 @@ fn collect_contexts_from_assets() -> Vec<SharedString> { let mut contexts = contexts.into_iter().collect::<Vec<_>>(); contexts.sort(); - return contexts; + contexts } impl SerializableItem for KeymapEditor { diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/settings_ui/src/ui_components/keystroke_input.rs index f23d80931c..1b8010853e 100644 --- a/crates/settings_ui/src/ui_components/keystroke_input.rs +++ b/crates/settings_ui/src/ui_components/keystroke_input.rs @@ -19,7 +19,7 @@ actions!( ] ); -const KEY_CONTEXT_VALUE: &'static str = "KeystrokeInput"; +const KEY_CONTEXT_VALUE: &str = "KeystrokeInput"; const CLOSE_KEYSTROKE_CAPTURE_END_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(300); @@ -116,19 +116,19 @@ impl KeystrokeInput { && self .keystrokes .last() - .map_or(false, |last| last.key.is_empty()) + .is_some_and(|last| last.key.is_empty()) { return &self.keystrokes[..self.keystrokes.len() - 1]; } - return &self.keystrokes; + &self.keystrokes } fn dummy(modifiers: Modifiers) -> Keystroke { - return Keystroke { + Keystroke { modifiers, key: "".to_string(), key_char: None, - }; + } } fn keystrokes_changed(&self, cx: &mut Context<Self>) { @@ -182,7 +182,7 @@ impl KeystrokeInput { fn end_close_keystrokes_capture(&mut self) -> Option<usize> { self.close_keystrokes.take(); self.clear_close_keystrokes_timer.take(); - return self.close_keystrokes_start.take(); + self.close_keystrokes_start.take() } fn handle_possible_close_keystroke( @@ -233,7 +233,7 @@ impl KeystrokeInput { return CloseKeystrokeResult::Partial; } self.end_close_keystrokes_capture(); - return CloseKeystrokeResult::None; + CloseKeystrokeResult::None } fn on_modifiers_changed( @@ -437,7 +437,7 @@ impl KeystrokeInput { // is a much more reliable check, as the intercept keystroke handlers are installed // on focus of the inner focus handle, thereby ensuring our recording state does // not get de-synced - return self.inner_focus_handle.is_focused(window); + self.inner_focus_handle.is_focused(window) } } @@ -811,7 +811,7 @@ mod tests { pub fn expect_keystrokes(&mut self, expected: &[&str]) -> &mut Self { let actual = self .input - .read_with(&mut self.cx, |input, _| input.keystrokes.clone()); + .read_with(&self.cx, |input, _| input.keystrokes.clone()); Self::expect_keystrokes_equal(&actual, expected); self } @@ -820,7 +820,7 @@ mod tests { pub fn expect_close_keystrokes(&mut self, expected: &[&str]) -> &mut Self { let actual = self .input - .read_with(&mut self.cx, |input, _| input.close_keystrokes.clone()) + .read_with(&self.cx, |input, _| input.close_keystrokes.clone()) .unwrap_or_default(); Self::expect_keystrokes_equal(&actual, expected); self @@ -934,7 +934,7 @@ mod tests { let change_tracker = KeystrokeUpdateTracker::new(self.input.clone(), &mut self.cx); let result = self.input.update_in(&mut self.cx, cb); KeystrokeUpdateTracker::finish(change_tracker, &self.cx); - return result; + result } } diff --git a/crates/settings_ui/src/ui_components/table.rs b/crates/settings_ui/src/ui_components/table.rs index ba7ae66653..cb0332c868 100644 --- a/crates/settings_ui/src/ui_components/table.rs +++ b/crates/settings_ui/src/ui_components/table.rs @@ -99,7 +99,7 @@ impl TableInteractionState { let mut column_ix = 0; let resizable_columns_slice = *resizable_columns; - let mut resizable_columns = resizable_columns.into_iter(); + let mut resizable_columns = resizable_columns.iter(); let dividers = intersperse_with(spacers, || { window.with_id(column_ix, |window| { @@ -440,7 +440,7 @@ impl<const COLS: usize> ColumnWidths<COLS> { } widths[col_idx] = widths[col_idx] + (diff - diff_remaining); - return diff_remaining; + diff_remaining } } @@ -510,7 +510,7 @@ impl<const COLS: usize> Table<COLS> { ) -> Self { self.rows = TableContents::UniformList(UniformListData { element_id: id.into(), - row_count: row_count, + row_count, render_item_fn: Box::new(render_item_fn), }); self diff --git a/crates/snippet/src/snippet.rs b/crates/snippet/src/snippet.rs index 6a673fe08b..4be4281d9a 100644 --- a/crates/snippet/src/snippet.rs +++ b/crates/snippet/src/snippet.rs @@ -33,7 +33,7 @@ impl Snippet { choices: None, }; - if !tabstops.last().map_or(false, |t| *t == end_tabstop) { + if !tabstops.last().is_some_and(|t| *t == end_tabstop) { tabstops.push(end_tabstop); } } diff --git a/crates/snippet_provider/src/lib.rs b/crates/snippet_provider/src/lib.rs index d1112a8d00..eac06924a7 100644 --- a/crates/snippet_provider/src/lib.rs +++ b/crates/snippet_provider/src/lib.rs @@ -69,18 +69,18 @@ async fn process_updates( entries: Vec<PathBuf>, mut cx: AsyncApp, ) -> Result<()> { - let fs = this.read_with(&mut cx, |this, _| this.fs.clone())?; + let fs = this.read_with(&cx, |this, _| this.fs.clone())?; for entry_path in entries { - if !entry_path + if entry_path .extension() - .map_or(false, |extension| extension == "json") + .is_none_or(|extension| extension != "json") { continue; } let entry_metadata = fs.metadata(&entry_path).await; // Entry could have been removed, in which case we should no longer show completions for it. let entry_exists = entry_metadata.is_ok(); - if entry_metadata.map_or(false, |entry| entry.map_or(false, |e| e.is_dir)) { + if entry_metadata.is_ok_and(|entry| entry.is_some_and(|e| e.is_dir)) { // Don't process dirs. continue; } @@ -118,9 +118,9 @@ async fn process_updates( async fn initial_scan( this: WeakEntity<SnippetProvider>, path: Arc<Path>, - mut cx: AsyncApp, + cx: AsyncApp, ) -> Result<()> { - let fs = this.read_with(&mut cx, |this, _| this.fs.clone())?; + let fs = this.read_with(&cx, |this, _| this.fs.clone())?; let entries = fs.read_dir(&path).await; if let Ok(entries) = entries { let entries = entries diff --git a/crates/snippets_ui/src/snippets_ui.rs b/crates/snippets_ui/src/snippets_ui.rs index bf0ef63bff..db76ab6f9a 100644 --- a/crates/snippets_ui/src/snippets_ui.rs +++ b/crates/snippets_ui/src/snippets_ui.rs @@ -163,13 +163,12 @@ impl ScopeSelectorDelegate { for entry in read_dir { if let Some(entry) = entry.log_err() { let path = entry.path(); - if let (Some(stem), Some(extension)) = (path.file_stem(), path.extension()) { - if extension.to_os_string().to_str() == Some("json") { - if let Ok(file_name) = stem.to_os_string().into_string() { - existing_scopes - .insert(ScopeName::from(ScopeFileName(Cow::Owned(file_name)))); - } - } + if let (Some(stem), Some(extension)) = (path.file_stem(), path.extension()) + && extension.to_os_string().to_str() == Some("json") + && let Ok(file_name) = stem.to_os_string().into_string() + { + existing_scopes + .insert(ScopeName::from(ScopeFileName(Cow::Owned(file_name)))); } } } diff --git a/crates/sqlez/src/connection.rs b/crates/sqlez/src/connection.rs index f56ae2427d..228bd4c6a2 100644 --- a/crates/sqlez/src/connection.rs +++ b/crates/sqlez/src/connection.rs @@ -213,38 +213,37 @@ impl Connection { fn parse_alter_table(remaining_sql_str: &str) -> Option<(String, String)> { let remaining_sql_str = remaining_sql_str.to_lowercase(); - if remaining_sql_str.starts_with("alter") { - if let Some(table_offset) = remaining_sql_str.find("table") { - let after_table_offset = table_offset + "table".len(); - let table_to_alter = remaining_sql_str - .chars() - .skip(after_table_offset) - .skip_while(|c| c.is_whitespace()) - .take_while(|c| !c.is_whitespace()) - .collect::<String>(); - if !table_to_alter.is_empty() { - let column_name = - if let Some(rename_offset) = remaining_sql_str.find("rename column") { - let after_rename_offset = rename_offset + "rename column".len(); - remaining_sql_str - .chars() - .skip(after_rename_offset) - .skip_while(|c| c.is_whitespace()) - .take_while(|c| !c.is_whitespace()) - .collect::<String>() - } else if let Some(drop_offset) = remaining_sql_str.find("drop column") { - let after_drop_offset = drop_offset + "drop column".len(); - remaining_sql_str - .chars() - .skip(after_drop_offset) - .skip_while(|c| c.is_whitespace()) - .take_while(|c| !c.is_whitespace()) - .collect::<String>() - } else { - "__place_holder_column_for_syntax_checking".to_string() - }; - return Some((table_to_alter, column_name)); - } + if remaining_sql_str.starts_with("alter") + && let Some(table_offset) = remaining_sql_str.find("table") + { + let after_table_offset = table_offset + "table".len(); + let table_to_alter = remaining_sql_str + .chars() + .skip(after_table_offset) + .skip_while(|c| c.is_whitespace()) + .take_while(|c| !c.is_whitespace()) + .collect::<String>(); + if !table_to_alter.is_empty() { + let column_name = if let Some(rename_offset) = remaining_sql_str.find("rename column") { + let after_rename_offset = rename_offset + "rename column".len(); + remaining_sql_str + .chars() + .skip(after_rename_offset) + .skip_while(|c| c.is_whitespace()) + .take_while(|c| !c.is_whitespace()) + .collect::<String>() + } else if let Some(drop_offset) = remaining_sql_str.find("drop column") { + let after_drop_offset = drop_offset + "drop column".len(); + remaining_sql_str + .chars() + .skip(after_drop_offset) + .skip_while(|c| c.is_whitespace()) + .take_while(|c| !c.is_whitespace()) + .collect::<String>() + } else { + "__place_holder_column_for_syntax_checking".to_string() + }; + return Some((table_to_alter, column_name)); } } None diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index fd0be97ff6..aad3875410 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -109,15 +109,13 @@ static ALL_STORY_SELECTORS: OnceLock<Vec<StorySelector>> = OnceLock::new(); impl ValueEnum for StorySelector { fn value_variants<'a>() -> &'a [Self] { - let stories = ALL_STORY_SELECTORS.get_or_init(|| { + (ALL_STORY_SELECTORS.get_or_init(|| { let component_stories = ComponentStory::iter().map(StorySelector::Component); component_stories .chain(std::iter::once(StorySelector::KitchenSink)) .collect::<Vec<_>>() - }); - - stories + })) as _ } fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> { diff --git a/crates/streaming_diff/src/streaming_diff.rs b/crates/streaming_diff/src/streaming_diff.rs index f7649b1bf1..704164e01e 100644 --- a/crates/streaming_diff/src/streaming_diff.rs +++ b/crates/streaming_diff/src/streaming_diff.rs @@ -303,10 +303,10 @@ impl LineDiff { self.flush_insert(old_text); self.buffered_insert.push_str(suffix); } else { - self.buffered_insert.push_str(&text); + self.buffered_insert.push_str(text); } } else { - self.buffered_insert.push_str(&text); + self.buffered_insert.push_str(text); if !text.ends_with('\n') { self.flush_insert(old_text); } @@ -523,7 +523,7 @@ mod tests { apply_line_operations(old_text, &new_text, &expected_line_ops) ); - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!(line_ops, expected_line_ops); } @@ -534,7 +534,7 @@ mod tests { CharOperation::Keep { bytes: 5 }, CharOperation::Delete { bytes: 4 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -559,7 +559,7 @@ mod tests { text: "\ncccc".into(), }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -582,7 +582,7 @@ mod tests { CharOperation::Delete { bytes: 5 }, CharOperation::Keep { bytes: 4 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -609,7 +609,7 @@ mod tests { }, CharOperation::Keep { bytes: 5 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -638,7 +638,7 @@ mod tests { text: "\nEEEE".into(), }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -664,7 +664,7 @@ mod tests { CharOperation::Insert { text: "A".into() }, CharOperation::Keep { bytes: 10 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -689,7 +689,7 @@ mod tests { CharOperation::Keep { bytes: 4 }, ]; let new_text = apply_char_operations(old_text, &char_ops); - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -710,7 +710,7 @@ mod tests { CharOperation::Insert { text: "\n".into() }, CharOperation::Keep { bytes: 9 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -733,7 +733,7 @@ mod tests { CharOperation::Delete { bytes: 1 }, CharOperation::Keep { bytes: 4 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -759,7 +759,7 @@ mod tests { }, CharOperation::Keep { bytes: 4 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -783,7 +783,7 @@ mod tests { CharOperation::Delete { bytes: 2 }, CharOperation::Keep { bytes: 4 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ @@ -814,7 +814,7 @@ mod tests { }, CharOperation::Keep { bytes: 6 }, ]; - let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + let line_ops = char_ops_to_line_ops(old_text, &char_ops); assert_eq!( line_ops, vec![ diff --git a/crates/sum_tree/src/cursor.rs b/crates/sum_tree/src/cursor.rs index 50a556a6d2..53458b65ec 100644 --- a/crates/sum_tree/src/cursor.rs +++ b/crates/sum_tree/src/cursor.rs @@ -530,10 +530,10 @@ where debug_assert!(self.stack.is_empty() || self.stack.last().unwrap().tree.0.is_leaf()); let mut end = self.position.clone(); - if bias == Bias::Left { - if let Some(summary) = self.item_summary() { - end.add_summary(summary, self.cx); - } + if bias == Bias::Left + && let Some(summary) = self.item_summary() + { + end.add_summary(summary, self.cx); } target.cmp(&end, self.cx) == Ordering::Equal diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index 3a12e3a681..710fdd4fbf 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -94,9 +94,7 @@ impl<'a, S: Summary, D: Dimension<'a, S> + Ord> SeekTarget<'a, S, D> for D { } impl<'a, T: Summary> Dimension<'a, T> for () { - fn zero(_: &T::Context) -> Self { - () - } + fn zero(_: &T::Context) -> Self {} fn add_summary(&mut self, _: &'a T, _: &T::Context) {} } @@ -674,11 +672,11 @@ impl<T: KeyedItem> SumTree<T> { *self = { let mut cursor = self.cursor::<T::Key>(cx); let mut new_tree = cursor.slice(&item.key(), Bias::Left); - if let Some(cursor_item) = cursor.item() { - if cursor_item.key() == item.key() { - replaced = Some(cursor_item.clone()); - cursor.next(); - } + if let Some(cursor_item) = cursor.item() + && cursor_item.key() == item.key() + { + replaced = Some(cursor_item.clone()); + cursor.next(); } new_tree.push(item, cx); new_tree.append(cursor.suffix(), cx); @@ -692,11 +690,11 @@ impl<T: KeyedItem> SumTree<T> { *self = { let mut cursor = self.cursor::<T::Key>(cx); let mut new_tree = cursor.slice(key, Bias::Left); - if let Some(item) = cursor.item() { - if item.key() == *key { - removed = Some(item.clone()); - cursor.next(); - } + if let Some(item) = cursor.item() + && item.key() == *key + { + removed = Some(item.clone()); + cursor.next(); } new_tree.append(cursor.suffix(), cx); new_tree @@ -728,7 +726,7 @@ impl<T: KeyedItem> SumTree<T> { if old_item .as_ref() - .map_or(false, |old_item| old_item.key() < new_key) + .is_some_and(|old_item| old_item.key() < new_key) { new_tree.extend(buffered_items.drain(..), cx); let slice = cursor.slice(&new_key, Bias::Left); @@ -736,11 +734,11 @@ impl<T: KeyedItem> SumTree<T> { old_item = cursor.item(); } - if let Some(old_item) = old_item { - if old_item.key() == new_key { - removed.push(old_item.clone()); - cursor.next(); - } + if let Some(old_item) = old_item + && old_item.key() == new_key + { + removed.push(old_item.clone()); + cursor.next(); } match edit { diff --git a/crates/supermaven/src/supermaven.rs b/crates/supermaven/src/supermaven.rs index a31b96d882..743c0d4c7d 100644 --- a/crates/supermaven/src/supermaven.rs +++ b/crates/supermaven/src/supermaven.rs @@ -243,7 +243,7 @@ fn find_relevant_completion<'a>( None => continue 'completions, }; - if best_completion.map_or(false, |best| best.len() > trimmed_completion.len()) { + if best_completion.is_some_and(|best| best.len() > trimmed_completion.len()) { continue; } diff --git a/crates/supermaven_api/src/supermaven_api.rs b/crates/supermaven_api/src/supermaven_api.rs index 61d14d5dc7..c4b1409d64 100644 --- a/crates/supermaven_api/src/supermaven_api.rs +++ b/crates/supermaven_api/src/supermaven_api.rs @@ -221,9 +221,7 @@ pub fn version_path(version: u64) -> PathBuf { } pub async fn has_version(version_path: &Path) -> bool { - fs::metadata(version_path) - .await - .map_or(false, |m| m.is_file()) + fs::metadata(version_path).await.is_ok_and(|m| m.is_file()) } pub async fn get_supermaven_agent_path(client: Arc<dyn HttpClient>) -> Result<PathBuf> { diff --git a/crates/svg_preview/src/svg_preview_view.rs b/crates/svg_preview/src/svg_preview_view.rs index 327856d749..12dd97f0c8 100644 --- a/crates/svg_preview/src/svg_preview_view.rs +++ b/crates/svg_preview/src/svg_preview_view.rs @@ -32,79 +32,74 @@ pub enum SvgPreviewMode { impl SvgPreviewView { pub fn register(workspace: &mut Workspace, _window: &mut Window, _cx: &mut Context<Workspace>) { workspace.register_action(move |workspace, _: &OpenPreview, window, cx| { - if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) { - if Self::is_svg_file(&editor, cx) { - let view = Self::create_svg_view( - SvgPreviewMode::Default, - workspace, - editor.clone(), - window, - cx, - ); - workspace.active_pane().update(cx, |pane, cx| { - if let Some(existing_view_idx) = - Self::find_existing_preview_item_idx(pane, &editor, cx) - { - pane.activate_item(existing_view_idx, true, true, window, cx); - } else { - pane.add_item(Box::new(view), true, true, None, window, cx) - } - }); - cx.notify(); - } + if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) + && Self::is_svg_file(&editor, cx) + { + let view = Self::create_svg_view( + SvgPreviewMode::Default, + workspace, + editor.clone(), + window, + cx, + ); + workspace.active_pane().update(cx, |pane, cx| { + if let Some(existing_view_idx) = + Self::find_existing_preview_item_idx(pane, &editor, cx) + { + pane.activate_item(existing_view_idx, true, true, window, cx); + } else { + pane.add_item(Box::new(view), true, true, None, window, cx) + } + }); + cx.notify(); } }); workspace.register_action(move |workspace, _: &OpenPreviewToTheSide, window, cx| { - if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) { - if Self::is_svg_file(&editor, cx) { - let editor_clone = editor.clone(); - let view = Self::create_svg_view( - SvgPreviewMode::Default, - workspace, - editor_clone, - window, - cx, - ); - let pane = workspace - .find_pane_in_direction(workspace::SplitDirection::Right, cx) - .unwrap_or_else(|| { - workspace.split_pane( - workspace.active_pane().clone(), - workspace::SplitDirection::Right, - window, - cx, - ) - }); - pane.update(cx, |pane, cx| { - if let Some(existing_view_idx) = - Self::find_existing_preview_item_idx(pane, &editor, cx) - { - pane.activate_item(existing_view_idx, true, true, window, cx); - } else { - pane.add_item(Box::new(view), false, false, None, window, cx) - } + if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) + && Self::is_svg_file(&editor, cx) + { + let editor_clone = editor.clone(); + let view = Self::create_svg_view( + SvgPreviewMode::Default, + workspace, + editor_clone, + window, + cx, + ); + let pane = workspace + .find_pane_in_direction(workspace::SplitDirection::Right, cx) + .unwrap_or_else(|| { + workspace.split_pane( + workspace.active_pane().clone(), + workspace::SplitDirection::Right, + window, + cx, + ) }); - cx.notify(); - } + pane.update(cx, |pane, cx| { + if let Some(existing_view_idx) = + Self::find_existing_preview_item_idx(pane, &editor, cx) + { + pane.activate_item(existing_view_idx, true, true, window, cx); + } else { + pane.add_item(Box::new(view), false, false, None, window, cx) + } + }); + cx.notify(); } }); workspace.register_action(move |workspace, _: &OpenFollowingPreview, window, cx| { - if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) { - if Self::is_svg_file(&editor, cx) { - let view = Self::create_svg_view( - SvgPreviewMode::Follow, - workspace, - editor, - window, - cx, - ); - workspace.active_pane().update(cx, |pane, cx| { - pane.add_item(Box::new(view), true, true, None, window, cx) - }); - cx.notify(); - } + if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) + && Self::is_svg_file(&editor, cx) + { + let view = + Self::create_svg_view(SvgPreviewMode::Follow, workspace, editor, window, cx); + workspace.active_pane().update(cx, |pane, cx| { + pane.add_item(Box::new(view), true, true, None, window, cx) + }); + cx.notify(); } }); } @@ -162,18 +157,15 @@ impl SvgPreviewView { &active_editor, window, |this: &mut SvgPreviewView, _editor, event: &EditorEvent, window, cx| { - match event { - EditorEvent::Saved => { - // Remove cached image to force reload - if let Some(svg_path) = &this.svg_path { - let resource = Resource::Path(svg_path.clone().into()); - this.image_cache.update(cx, |cache, cx| { - cache.remove(&resource, window, cx); - }); - } - cx.notify(); + if event == &EditorEvent::Saved { + // Remove cached image to force reload + if let Some(svg_path) = &this.svg_path { + let resource = Resource::Path(svg_path.clone().into()); + this.image_cache.update(cx, |cache, cx| { + cache.remove(&resource, window, cx); + }); } - _ => {} + cx.notify(); } }, ); @@ -189,25 +181,18 @@ impl SvgPreviewView { event: &workspace::Event, _window, cx| { - match event { - workspace::Event::ActiveItemChanged => { - let workspace_read = workspace.read(cx); - if let Some(active_item) = workspace_read.active_item(cx) { - if let Some(editor_entity) = - active_item.downcast::<Editor>() - { - if Self::is_svg_file(&editor_entity, cx) { - let new_path = - Self::get_svg_path(&editor_entity, cx); - if this.svg_path != new_path { - this.svg_path = new_path; - cx.notify(); - } - } - } + if let workspace::Event::ActiveItemChanged = event { + let workspace_read = workspace.read(cx); + if let Some(active_item) = workspace_read.active_item(cx) + && let Some(editor_entity) = active_item.downcast::<Editor>() + && Self::is_svg_file(&editor_entity, cx) + { + let new_path = Self::get_svg_path(&editor_entity, cx); + if this.svg_path != new_path { + this.svg_path = new_path; + cx.notify(); } } - _ => {} } }, ) @@ -232,15 +217,15 @@ impl SvgPreviewView { { let app = cx.borrow(); let buffer = editor.read(app).buffer().read(app); - if let Some(buffer) = buffer.as_singleton() { - if let Some(file) = buffer.read(app).file() { - return file - .path() - .extension() - .and_then(|ext| ext.to_str()) - .map(|ext| ext.eq_ignore_ascii_case("svg")) - .unwrap_or(false); - } + if let Some(buffer) = buffer.as_singleton() + && let Some(file) = buffer.read(app).file() + { + return file + .path() + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.eq_ignore_ascii_case("svg")) + .unwrap_or(false); } false } diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index f20f55975e..38089670e2 100644 --- a/crates/task/src/debug_format.rs +++ b/crates/task/src/debug_format.rs @@ -299,13 +299,12 @@ impl DebugTaskFile { if let Some(properties) = template_object .get_mut("properties") .and_then(|value| value.as_object_mut()) + && properties.remove("label").is_none() { - if properties.remove("label").is_none() { - debug_panic!( - "Generated TaskTemplate json schema did not have expected 'label' field. \ + debug_panic!( + "Generated TaskTemplate json schema did not have expected 'label' field. \ Schema of 2nd alternative is: {template_object:?}" - ); - } + ); } if let Some(arr) = template_object diff --git a/crates/task/src/shell_builder.rs b/crates/task/src/shell_builder.rs index b8c49d4230..5ed29fd733 100644 --- a/crates/task/src/shell_builder.rs +++ b/crates/task/src/shell_builder.rs @@ -237,13 +237,11 @@ impl ShellBuilder { task_args: &Vec<String>, ) -> (String, Vec<String>) { if let Some(task_command) = task_command { - let combined_command = task_args - .into_iter() - .fold(task_command, |mut command, arg| { - command.push(' '); - command.push_str(&self.kind.to_shell_variable(arg)); - command - }); + let combined_command = task_args.iter().fold(task_command, |mut command, arg| { + command.push(' '); + command.push_str(&self.kind.to_shell_variable(arg)); + command + }); self.args .extend(self.kind.args_for_shell(self.interactive, combined_command)); diff --git a/crates/task/src/vscode_debug_format.rs b/crates/task/src/vscode_debug_format.rs index 2990716686..5e21cd6530 100644 --- a/crates/task/src/vscode_debug_format.rs +++ b/crates/task/src/vscode_debug_format.rs @@ -30,12 +30,12 @@ impl VsCodeDebugTaskDefinition { let label = replacer.replace(&self.name); let mut config = replacer.replace_value(self.other_attributes); let adapter = task_type_to_adapter_name(&self.r#type); - if let Some(config) = config.as_object_mut() { - if adapter == "JavaScript" { - config.insert("type".to_owned(), self.r#type.clone().into()); - if let Some(port) = self.port.take() { - config.insert("port".to_owned(), port.into()); - } + if let Some(config) = config.as_object_mut() + && adapter == "JavaScript" + { + config.insert("type".to_owned(), self.r#type.clone().into()); + if let Some(port) = self.port.take() { + config.insert("port".to_owned(), port.into()); } } let definition = DebugScenario { @@ -131,7 +131,7 @@ mod tests { } "#; let parsed: VsCodeDebugTaskFile = - serde_json_lenient::from_str(&raw).expect("deserializing launch.json"); + serde_json_lenient::from_str(raw).expect("deserializing launch.json"); let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates"); pretty_assertions::assert_eq!( zed, diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index c4b0931c35..9fbdc152f3 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -550,7 +550,7 @@ impl PickerDelegate for TasksModalDelegate { list_item.tooltip(move |_, _| item_label.clone()) }) .map(|item| { - let item = if matches!(source_kind, TaskSourceKind::UserInput) + if matches!(source_kind, TaskSourceKind::UserInput) || Some(ix) <= self.divider_index { let task_index = hit.candidate_id; @@ -579,8 +579,7 @@ impl PickerDelegate for TasksModalDelegate { item.end_hover_slot(delete_button) } else { item - }; - item + } }) .toggle_state(selected) .child(highlighted_location.render(window, cx)), diff --git a/crates/tasks_ui/src/tasks_ui.rs b/crates/tasks_ui/src/tasks_ui.rs index 0b3f70e6bc..a4fdc24e17 100644 --- a/crates/tasks_ui/src/tasks_ui.rs +++ b/crates/tasks_ui/src/tasks_ui.rs @@ -148,9 +148,9 @@ pub fn toggle_modal( ) -> Task<()> { let task_store = workspace.project().read(cx).task_store().clone(); let workspace_handle = workspace.weak_handle(); - let can_open_modal = workspace.project().update(cx, |project, cx| { - project.is_local() || project.ssh_connection_string(cx).is_some() || project.is_via_ssh() - }); + let can_open_modal = workspace + .project() + .read_with(cx, |project, _| !project.is_via_collab()); if can_open_modal { let task_contexts = task_contexts(workspace, window, cx); cx.spawn_in(window, async move |workspace, cx| { @@ -227,10 +227,10 @@ where tasks.retain_mut(|(task_source_kind, target_task)| { if predicate((task_source_kind, target_task)) { - if let Some(overrides) = &overrides { - if let Some(target_override) = overrides.reveal_target { - target_task.reveal_target = target_override; - } + if let Some(overrides) = &overrides + && let Some(target_override) = overrides.reveal_target + { + target_task.reveal_target = target_override; } workspace.schedule_task( task_source_kind.clone(), @@ -283,7 +283,7 @@ pub fn task_contexts( .project() .read(cx) .worktree_for_id(*worktree_id, cx) - .map_or(false, |worktree| is_visible_directory(&worktree, cx)) + .is_some_and(|worktree| is_visible_directory(&worktree, cx)) }) .or_else(|| { workspace @@ -343,11 +343,10 @@ pub fn task_contexts( task_contexts.lsp_task_sources = lsp_task_sources; task_contexts.latest_selection = latest_selection; - if let Some(editor_context_task) = editor_context_task { - if let Some(editor_context) = editor_context_task.await { - task_contexts.active_item_context = - Some((active_worktree, location, editor_context)); - } + if let Some(editor_context_task) = editor_context_task + && let Some(editor_context) = editor_context_task.await + { + task_contexts.active_item_context = Some((active_worktree, location, editor_context)); } if let Some(active_worktree) = active_worktree { @@ -373,7 +372,7 @@ pub fn task_contexts( fn is_visible_directory(worktree: &Entity<Worktree>, cx: &App) -> bool { let worktree = worktree.read(cx); - worktree.is_visible() && worktree.root_entry().map_or(false, |entry| entry.is_dir()) + worktree.is_visible() && worktree.root_entry().is_some_and(|entry| entry.is_dir()) } fn worktree_context(worktree_abs_path: &Path) -> TaskContext { diff --git a/crates/telemetry/src/telemetry.rs b/crates/telemetry/src/telemetry.rs index f8f7d5851e..ac43457c33 100644 --- a/crates/telemetry/src/telemetry.rs +++ b/crates/telemetry/src/telemetry.rs @@ -55,7 +55,6 @@ macro_rules! serialize_property { pub fn send_event(event: Event) { if let Some(queue) = TELEMETRY_QUEUE.get() { queue.unbounded_send(event).ok(); - return; } } diff --git a/crates/terminal/src/pty_info.rs b/crates/terminal/src/pty_info.rs index 802470493c..a1a559051a 100644 --- a/crates/terminal/src/pty_info.rs +++ b/crates/terminal/src/pty_info.rs @@ -122,7 +122,7 @@ impl PtyProcessInfo { } pub(crate) fn kill_current_process(&mut self) -> bool { - self.refresh().map_or(false, |process| process.kill()) + self.refresh().is_some_and(|process| process.kill()) } fn load(&mut self) -> Option<ProcessInfo> { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 86728cc11c..16c1efabba 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -890,15 +890,15 @@ impl Terminal { if self.vi_mode_enabled { match *scroll { AlacScroll::Delta(delta) => { - term.vi_mode_cursor = term.vi_mode_cursor.scroll(&term, delta); + term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, delta); } AlacScroll::PageUp => { let lines = term.screen_lines() as i32; - term.vi_mode_cursor = term.vi_mode_cursor.scroll(&term, lines); + term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, lines); } AlacScroll::PageDown => { let lines = -(term.screen_lines() as i32); - term.vi_mode_cursor = term.vi_mode_cursor.scroll(&term, lines); + term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, lines); } AlacScroll::Top => { let point = AlacPoint::new(term.topmost_line(), Column(0)); @@ -1051,15 +1051,16 @@ impl Terminal { navigation_target: MaybeNavigationTarget, cx: &mut Context<Self>, ) { - if let Some(prev_word) = prev_word { - if prev_word.word == word && prev_word.word_match == word_match { - self.last_content.last_hovered_word = Some(HoveredWord { - word, - word_match, - id: prev_word.id, - }); - return; - } + if let Some(prev_word) = prev_word + && prev_word.word == word + && prev_word.word_match == word_match + { + self.last_content.last_hovered_word = Some(HoveredWord { + word, + word_match, + id: prev_word.id, + }); + return; } self.last_content.last_hovered_word = Some(HoveredWord { @@ -1298,23 +1299,19 @@ impl Terminal { let selection = Selection::new(selection_type, point, side); self.events .push_back(InternalEvent::SetSelection(Some((selection, point)))); - return; } "escape" => { self.events.push_back(InternalEvent::SetSelection(None)); - return; } "y" => { self.copy(Some(false)); - return; } "i" => { self.scroll_to_bottom(); self.toggle_vi_mode(); - return; } _ => {} } @@ -1517,12 +1514,11 @@ impl Terminal { self.last_content.display_offset, ); - if self.mouse_changed(point, side) { - if let Some(bytes) = + if self.mouse_changed(point, side) + && let Some(bytes) = mouse_moved_report(point, e.pressed_button, e.modifiers, self.last_content.mode) - { - self.pty_tx.notify(bytes); - } + { + self.pty_tx.notify(bytes); } } else if e.modifiers.secondary() { self.word_from_position(e.position); @@ -1864,10 +1860,10 @@ impl Terminal { } pub fn kill_active_task(&mut self) { - if let Some(task) = self.task() { - if task.status == TaskStatus::Running { - self.pty_info.kill_current_process(); - } + if let Some(task) = self.task() + && task.status == TaskStatus::Running + { + self.pty_info.kill_current_process(); } } @@ -1891,11 +1887,11 @@ impl Terminal { let e: Option<ExitStatus> = error_code.map(|code| { #[cfg(unix)] { - return std::os::unix::process::ExitStatusExt::from_raw(code); + std::os::unix::process::ExitStatusExt::from_raw(code) } #[cfg(windows)] { - return std::os::windows::process::ExitStatusExt::from_raw(code as u32); + std::os::windows::process::ExitStatusExt::from_raw(code as u32) } }); @@ -2160,7 +2156,7 @@ mod tests { use gpui::{Pixels, Point, TestAppContext, bounds, point, size}; use rand::{Rng, distributions::Alphanumeric, rngs::ThreadRng, thread_rng}; - #[cfg_attr(windows, ignore = "TODO: fix on windows")] + #[ignore = "Test is flaky on macOS, and doesn't run on Windows"] #[gpui::test] async fn test_basic_terminal(cx: &mut TestAppContext) { cx.executor().allow_parking(); diff --git a/crates/terminal/src/terminal_hyperlinks.rs b/crates/terminal/src/terminal_hyperlinks.rs index e318ae21bd..9f565bd306 100644 --- a/crates/terminal/src/terminal_hyperlinks.rs +++ b/crates/terminal/src/terminal_hyperlinks.rs @@ -124,12 +124,12 @@ pub(super) fn find_from_grid_point<T: EventListener>( && file_path .chars() .nth(last_index - 1) - .map_or(false, |c| c.is_ascii_digit()); + .is_some_and(|c| c.is_ascii_digit()); let next_is_digit = last_index < file_path.len() - 1 && file_path .chars() .nth(last_index + 1) - .map_or(true, |c| c.is_ascii_digit()); + .is_none_or(|c| c.is_ascii_digit()); if prev_is_digit && !next_is_digit { let stripped_len = file_path.len() - last_index; word_match = Match::new( diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 3f89afffab..635e3e2ca5 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -325,10 +325,10 @@ impl settings::Settings for TerminalSettings { .and_then(|v| v.as_object()) { for (k, v) in env { - if v.is_null() { - if let Some(zed_env) = current.env.as_mut() { - zed_env.remove(k); - } + if v.is_null() + && let Some(zed_env) = current.env.as_mut() + { + zed_env.remove(k); } let Some(v) = v.as_str() else { continue }; if let Some(zed_env) = current.env.as_mut() { diff --git a/crates/terminal_view/src/color_contrast.rs b/crates/terminal_view/src/color_contrast.rs index fe4a881cea..522dca3e91 100644 --- a/crates/terminal_view/src/color_contrast.rs +++ b/crates/terminal_view/src/color_contrast.rs @@ -235,12 +235,10 @@ fn adjust_lightness_for_contrast( } else { high = mid; } + } else if should_go_darker { + high = mid; } else { - if should_go_darker { - high = mid; - } else { - low = mid; - } + low = mid; } // If we're close enough to the target, stop diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 6c1be9d5e7..1c38dbc877 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -583,15 +583,15 @@ impl TerminalElement { strikethrough, }; - if let Some((style, range)) = hyperlink { - if range.contains(&indexed.point) { - if let Some(underline) = style.underline { - result.underline = Some(underline); - } + if let Some((style, range)) = hyperlink + && range.contains(&indexed.point) + { + if let Some(underline) = style.underline { + result.underline = Some(underline); + } - if let Some(color) = style.color { - result.color = color; - } + if let Some(color) = style.color { + result.color = color; } } @@ -1275,9 +1275,9 @@ impl Element for TerminalElement { } let text_paint_time = text_paint_start.elapsed(); - if let Some(text_to_mark) = &marked_text_cloned { - if !text_to_mark.is_empty() { - if let Some(cursor_layout) = &original_cursor { + if let Some(text_to_mark) = &marked_text_cloned + && !text_to_mark.is_empty() + && let Some(cursor_layout) = &original_cursor { let ime_position = cursor_layout.bounding_rect(origin).origin; let mut ime_style = layout.base_text_style.clone(); ime_style.underline = Some(UnderlineStyle { @@ -1303,14 +1303,11 @@ impl Element for TerminalElement { .paint(ime_position, layout.dimensions.line_height, window, cx) .log_err(); } - } - } - if self.cursor_visible && marked_text_cloned.is_none() { - if let Some(mut cursor) = original_cursor { + if self.cursor_visible && marked_text_cloned.is_none() + && let Some(mut cursor) = original_cursor { cursor.paint(origin, window, cx); } - } if let Some(mut element) = block_below_cursor_element { element.paint(window, cx); @@ -1481,7 +1478,7 @@ pub fn is_blank(cell: &IndexedCell) -> bool { return false; } - return true; + true } fn to_highlighted_range_lines( diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 568dc1db2e..1d76f70152 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -236,7 +236,7 @@ impl TerminalPanel { ) -> Result<Entity<Self>> { let mut terminal_panel = None; - match workspace + if let Some((database_id, serialization_key)) = workspace .read_with(&cx, |workspace, _| { workspace .database_id() @@ -244,36 +244,29 @@ impl TerminalPanel { }) .ok() .flatten() + && let Some(serialized_panel) = cx + .background_spawn(async move { KEY_VALUE_STORE.read_kvp(&serialization_key) }) + .await + .log_err() + .flatten() + .map(|panel| serde_json::from_str::<SerializedTerminalPanel>(&panel)) + .transpose() + .log_err() + .flatten() + && let Ok(serialized) = workspace + .update_in(&mut cx, |workspace, window, cx| { + deserialize_terminal_panel( + workspace.weak_handle(), + workspace.project().clone(), + database_id, + serialized_panel, + window, + cx, + ) + })? + .await { - Some((database_id, serialization_key)) => { - if let Some(serialized_panel) = cx - .background_spawn(async move { KEY_VALUE_STORE.read_kvp(&serialization_key) }) - .await - .log_err() - .flatten() - .map(|panel| serde_json::from_str::<SerializedTerminalPanel>(&panel)) - .transpose() - .log_err() - .flatten() - { - if let Ok(serialized) = workspace - .update_in(&mut cx, |workspace, window, cx| { - deserialize_terminal_panel( - workspace.weak_handle(), - workspace.project().clone(), - database_id, - serialized_panel, - window, - cx, - ) - })? - .await - { - terminal_panel = Some(serialized); - } - } - } - _ => {} + terminal_panel = Some(serialized); } let terminal_panel = if let Some(panel) = terminal_panel { @@ -346,18 +339,16 @@ impl TerminalPanel { pane::Event::RemovedItem { .. } => self.serialize(cx), pane::Event::Remove { focus_on_pane } => { let pane_count_before_removal = self.center.panes().len(); - let _removal_result = self.center.remove(&pane); + let _removal_result = self.center.remove(pane); if pane_count_before_removal == 1 { self.center.first_pane().update(cx, |pane, cx| { pane.set_zoomed(false, cx); }); cx.emit(PanelEvent::Close); - } else { - if let Some(focus_on_pane) = - focus_on_pane.as_ref().or_else(|| self.center.panes().pop()) - { - focus_on_pane.focus_handle(cx).focus(window); - } + } else if let Some(focus_on_pane) = + focus_on_pane.as_ref().or_else(|| self.center.panes().pop()) + { + focus_on_pane.focus_handle(cx).focus(window); } } pane::Event::ZoomIn => { @@ -633,7 +624,7 @@ impl TerminalPanel { workspace .read(cx) .panes() - .into_iter() + .iter() .cloned() .flat_map(pane_terminal_views), ) @@ -898,9 +889,9 @@ impl TerminalPanel { } fn is_enabled(&self, cx: &App) -> bool { - self.workspace.upgrade().map_or(false, |workspace| { - is_enabled_in_workspace(workspace.read(cx), cx) - }) + self.workspace + .upgrade() + .is_some_and(|workspace| is_enabled_in_workspace(workspace.read(cx), cx)) } fn activate_pane_in_direction( @@ -1077,11 +1068,10 @@ pub fn new_terminal_pane( return ControlFlow::Break(()); } }; - } else if let Some(project_path) = item.project_path(cx) { - if let Some(entry_path) = project.read(cx).absolute_path(&project_path, cx) - { - add_paths_to_terminal(pane, &[entry_path], window, cx); - } + } else if let Some(project_path) = item.project_path(cx) + && let Some(entry_path) = project.read(cx).absolute_path(&project_path, cx) + { + add_paths_to_terminal(pane, &[entry_path], window, cx); } } } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>() { @@ -1103,10 +1093,8 @@ pub fn new_terminal_pane( { add_paths_to_terminal(pane, &[entry_path], window, cx); } - } else if is_local { - if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() { - add_paths_to_terminal(pane, paths.paths(), window, cx); - } + } else if is_local && let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() { + add_paths_to_terminal(pane, paths.paths(), window, cx); } ControlFlow::Break(()) @@ -1181,10 +1169,10 @@ impl Render for TerminalPanel { registrar.size_full().child(self.center.render( workspace.zoomed_item(), &workspace::PaneRenderContext { - follower_states: &&HashMap::default(), + follower_states: &HashMap::default(), active_call: workspace.active_call(), active_pane: &self.active_pane, - app_state: &workspace.app_state(), + app_state: workspace.app_state(), project: workspace.project(), workspace: &workspace.weak_handle(), }, @@ -1247,20 +1235,18 @@ impl Render for TerminalPanel { let panes = terminal_panel.center.panes(); if let Some(&pane) = panes.get(action.0) { window.focus(&pane.read(cx).focus_handle(cx)); - } else { - if let Some(new_pane) = - terminal_panel.new_pane_with_cloned_active_terminal(window, cx) - { - terminal_panel - .center - .split( - &terminal_panel.active_pane, - &new_pane, - SplitDirection::Right, - ) - .log_err(); - window.focus(&new_pane.focus_handle(cx)); - } + } else if let Some(new_pane) = + terminal_panel.new_pane_with_cloned_active_terminal(window, cx) + { + terminal_panel + .center + .split( + &terminal_panel.active_pane, + &new_pane, + SplitDirection::Right, + ) + .log_err(); + window.focus(&new_pane.focus_handle(cx)); } }), ) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index ca0a79ccfd..ec5e29c75c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -302,10 +302,10 @@ impl TerminalView { } else { let mut displayed_lines = total_lines; - if !self.focus_handle.is_focused(window) { - if let Some(max_lines) = max_lines_when_unfocused { - displayed_lines = displayed_lines.min(*max_lines) - } + if !self.focus_handle.is_focused(window) + && let Some(max_lines) = max_lines_when_unfocused + { + displayed_lines = displayed_lines.min(*max_lines) } ContentMode::Inline { @@ -379,9 +379,7 @@ impl TerminalView { .workspace .upgrade() .and_then(|workspace| workspace.read(cx).panel::<TerminalPanel>(cx)) - .map_or(false, |terminal_panel| { - terminal_panel.read(cx).assistant_enabled() - }); + .is_some_and(|terminal_panel| terminal_panel.read(cx).assistant_enabled()); let context_menu = ContextMenu::build(window, cx, |menu, _, _| { menu.context(self.focus_handle.clone()) .action("New Terminal", Box::new(NewTerminal)) @@ -1020,26 +1018,26 @@ fn subscribe_for_terminal_events( if let Some(opened_item) = opened_items.first() { if open_target.is_file() { - if let Some(Ok(opened_item)) = opened_item { - if let Some(row) = path_to_open.row { - let col = path_to_open.column.unwrap_or(0); - if let Some(active_editor) = - opened_item.downcast::<Editor>() - { - active_editor - .downgrade() - .update_in(cx, |editor, window, cx| { - editor.go_to_singleton_buffer_point( - language::Point::new( - row.saturating_sub(1), - col.saturating_sub(1), - ), - window, - cx, - ) - }) - .log_err(); - } + if let Some(Ok(opened_item)) = opened_item + && let Some(row) = path_to_open.row + { + let col = path_to_open.column.unwrap_or(0); + if let Some(active_editor) = + opened_item.downcast::<Editor>() + { + active_editor + .downgrade() + .update_in(cx, |editor, window, cx| { + editor.go_to_singleton_buffer_point( + language::Point::new( + row.saturating_sub(1), + col.saturating_sub(1), + ), + window, + cx, + ) + }) + .log_err(); } } } else if open_target.is_dir() { @@ -1185,17 +1183,17 @@ fn possible_open_target( } }; - if path_to_check.path.is_relative() { - if let Some(entry) = worktree.read(cx).entry_for_path(&path_to_check.path) { - return Task::ready(Some(OpenTarget::Worktree( - PathWithPosition { - path: worktree_root.join(&entry.path), - row: path_to_check.row, - column: path_to_check.column, - }, - entry.clone(), - ))); - } + if path_to_check.path.is_relative() + && let Some(entry) = worktree.read(cx).entry_for_path(&path_to_check.path) + { + return Task::ready(Some(OpenTarget::Worktree( + PathWithPosition { + path: worktree_root.join(&entry.path), + row: path_to_check.row, + column: path_to_check.column, + }, + entry.clone(), + ))); } paths_to_check.push(path_to_check); @@ -1292,11 +1290,11 @@ fn possible_open_target( let fs = workspace.read(cx).project().read(cx).fs().clone(); cx.background_spawn(async move { for mut path_to_check in fs_paths_to_check { - if let Some(fs_path_to_check) = fs.canonicalize(&path_to_check.path).await.ok() { - if let Some(metadata) = fs.metadata(&fs_path_to_check).await.ok().flatten() { - path_to_check.path = fs_path_to_check; - return Some(OpenTarget::File(path_to_check, metadata)); - } + if let Some(fs_path_to_check) = fs.canonicalize(&path_to_check.path).await.ok() + && let Some(metadata) = fs.metadata(&fs_path_to_check).await.ok().flatten() + { + path_to_check.path = fs_path_to_check; + return Some(OpenTarget::File(path_to_check, metadata)); } } @@ -1492,15 +1490,15 @@ impl Item for TerminalView { TaskStatus::Running => ( IconName::PlayFilled, Color::Disabled, - TerminalView::rerun_button(&terminal_task), + TerminalView::rerun_button(terminal_task), ), TaskStatus::Unknown => ( IconName::Warning, Color::Warning, - TerminalView::rerun_button(&terminal_task), + TerminalView::rerun_button(terminal_task), ), TaskStatus::Completed { success } => { - let rerun_button = TerminalView::rerun_button(&terminal_task); + let rerun_button = TerminalView::rerun_button(terminal_task); if *success { (IconName::Check, Color::Success, rerun_button) @@ -1827,7 +1825,8 @@ impl SearchableItem for TerminalView { // Selection head might have a value if there's a selection that isn't // associated with a match. Therefore, if there are no matches, we should // report None, no matter the state of the terminal - let res = if !matches.is_empty() { + + if !matches.is_empty() { if let Some(selection_head) = self.terminal().read(cx).selection_head { // If selection head is contained in a match. Return that match match direction { @@ -1867,9 +1866,7 @@ impl SearchableItem for TerminalView { } } else { None - }; - - res + } } fn replace( &mut self, diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index c4778216e0..becc5d9c0a 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -108,7 +108,7 @@ impl Anchor { fragment_cursor.seek(&Some(fragment_id), Bias::Left); fragment_cursor .item() - .map_or(false, |fragment| fragment.visible) + .is_some_and(|fragment| fragment.visible) } } } diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index 96fed17571..dcb35e9a92 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -57,7 +57,7 @@ where // Push the old edit if its new end is before the new edit's old start. if let Some(old_edit) = old_edit.as_ref() { let new_edit = new_edit.as_ref(); - if new_edit.map_or(true, |new_edit| old_edit.new.end < new_edit.old.start) { + if new_edit.is_none_or(|new_edit| old_edit.new.end < new_edit.old.start) { let catchup = old_edit.old.start - old_start; old_start += catchup; new_start += catchup; @@ -78,7 +78,7 @@ where // Push the new edit if its old end is before the old edit's new start. if let Some(new_edit) = new_edit.as_ref() { let old_edit = old_edit.as_ref(); - if old_edit.map_or(true, |old_edit| new_edit.old.end < old_edit.new.start) { + if old_edit.is_none_or(|old_edit| new_edit.old.end < old_edit.new.start) { let catchup = new_edit.new.start - new_start; old_start += catchup; new_start += catchup; diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 9f7e49d24d..705d3f1788 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -446,10 +446,10 @@ impl History { } fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) { - if let Some(transaction) = self.forget(transaction) { - if let Some(destination) = self.transaction_mut(destination) { - destination.edit_ids.extend(transaction.edit_ids); - } + if let Some(transaction) = self.forget(transaction) + && let Some(destination) = self.transaction_mut(destination) + { + destination.edit_ids.extend(transaction.edit_ids); } } @@ -1149,7 +1149,7 @@ impl Buffer { // Insert the new text before any existing fragments within the range. if !new_text.is_empty() { let mut old_start = old_fragments.start().1; - if old_fragments.item().map_or(false, |f| f.visible) { + if old_fragments.item().is_some_and(|f| f.visible) { old_start += fragment_start.0 - old_fragments.start().0.full_offset().0; } let new_start = new_fragments.summary().text.visible; @@ -1585,11 +1585,11 @@ impl Buffer { .map(Some) .chain([None]) .filter_map(move |range| { - if let Some((range, prev_range)) = range.as_ref().zip(prev_range.as_mut()) { - if prev_range.end == range.start { - prev_range.end = range.end; - return None; - } + if let Some((range, prev_range)) = range.as_ref().zip(prev_range.as_mut()) + && prev_range.end == range.start + { + prev_range.end = range.end; + return None; } let result = prev_range.clone(); prev_range = range; @@ -1685,10 +1685,10 @@ impl Buffer { rx = Some(channel.1); } async move { - if let Some(mut rx) = rx { - if rx.recv().await.is_none() { - anyhow::bail!("gave up waiting for version"); - } + if let Some(mut rx) = rx + && rx.recv().await.is_none() + { + anyhow::bail!("gave up waiting for version"); } Ok(()) } @@ -1834,7 +1834,7 @@ impl Buffer { let mut edits: Vec<(Range<usize>, Arc<str>)> = Vec::new(); let mut last_end = None; for _ in 0..edit_count { - if last_end.map_or(false, |last_end| last_end >= self.len()) { + if last_end.is_some_and(|last_end| last_end >= self.len()) { break; } let new_start = last_end.map_or(0, |last_end| last_end + 1); @@ -2671,7 +2671,7 @@ impl<D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Ed if pending_edit .as_ref() - .map_or(false, |(change, _)| change.new.end < self.new_end) + .is_some_and(|(change, _)| change.new.end < self.new_end) { break; } diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index e9e8e2d0db..13786aca57 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -34,10 +34,10 @@ pub(crate) fn apply_status_color_defaults(status: &mut StatusColorsRefinement) { (&status.error, &mut status.error_background), (&status.hidden, &mut status.hidden_background), ] { - if bg_color.is_none() { - if let Some(fg_color) = fg_color { - *bg_color = Some(fg_color.opacity(0.25)); - } + if bg_color.is_none() + && let Some(fg_color) = fg_color + { + *bg_color = Some(fg_color.opacity(0.25)); } } } diff --git a/crates/theme/src/icon_theme.rs b/crates/theme/src/icon_theme.rs index 5bd69c1733..c21709559a 100644 --- a/crates/theme/src/icon_theme.rs +++ b/crates/theme/src/icon_theme.rs @@ -398,7 +398,7 @@ static DEFAULT_ICON_THEME: LazyLock<Arc<IconTheme>> = LazyLock::new(|| { }, file_stems: icon_keys_by_association(FILE_STEMS_BY_ICON_KEY), file_suffixes: icon_keys_by_association(FILE_SUFFIXES_BY_ICON_KEY), - file_icons: HashMap::from_iter(FILE_ICONS.into_iter().map(|(ty, path)| { + file_icons: HashMap::from_iter(FILE_ICONS.iter().map(|(ty, path)| { ( ty.to_string(), IconDefinition { diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index 98f0eeb6cc..d8b0b8dc6b 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/crates/title_bar/src/application_menu.rs @@ -269,32 +269,31 @@ impl Render for ApplicationMenu { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let all_menus_shown = self.all_menus_shown(cx); - if let Some(pending_menu_open) = self.pending_menu_open.take() { - if let Some(entry) = self + if let Some(pending_menu_open) = self.pending_menu_open.take() + && let Some(entry) = self .entries .iter() .find(|entry| entry.menu.name == pending_menu_open && !entry.handle.is_deployed()) - { - let handle_to_show = entry.handle.clone(); - let handles_to_hide: Vec<_> = self - .entries - .iter() - .filter(|e| e.menu.name != pending_menu_open && e.handle.is_deployed()) - .map(|e| e.handle.clone()) - .collect(); + { + let handle_to_show = entry.handle.clone(); + let handles_to_hide: Vec<_> = self + .entries + .iter() + .filter(|e| e.menu.name != pending_menu_open && e.handle.is_deployed()) + .map(|e| e.handle.clone()) + .collect(); - if handles_to_hide.is_empty() { - // We need to wait for the next frame to show all menus first, - // before we can handle show/hide operations - window.on_next_frame(move |window, cx| { - handles_to_hide.iter().for_each(|handle| handle.hide(cx)); - window.defer(cx, move |window, cx| handle_to_show.show(window, cx)); - }); - } else { - // Since menus are already shown, we can directly handle show/hide operations + if handles_to_hide.is_empty() { + // We need to wait for the next frame to show all menus first, + // before we can handle show/hide operations + window.on_next_frame(move |window, cx| { handles_to_hide.iter().for_each(|handle| handle.hide(cx)); - cx.defer_in(window, move |_, window, cx| handle_to_show.show(window, cx)); - } + window.defer(cx, move |window, cx| handle_to_show.show(window, cx)); + }); + } else { + // Since menus are already shown, we can directly handle show/hide operations + handles_to_hide.iter().for_each(|handle| handle.hide(cx)); + cx.defer_in(window, move |_, window, cx| handle_to_show.show(window, cx)); } } diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index 74d60a6d66..5be68afeb4 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -14,7 +14,6 @@ use ui::{ Avatar, AvatarAudioStatusIndicator, ContextMenu, ContextMenuItem, Divider, DividerColor, Facepile, PopoverMenu, SplitButton, SplitButtonStyle, TintColor, Tooltip, prelude::*, }; -use util::maybe; use workspace::notifications::DetachAndPromptErr; use crate::TitleBar; @@ -32,52 +31,59 @@ actions!( ); fn toggle_screen_sharing( - screen: Option<Rc<dyn ScreenCaptureSource>>, + screen: anyhow::Result<Option<Rc<dyn ScreenCaptureSource>>>, window: &mut Window, cx: &mut App, ) { let call = ActiveCall::global(cx).read(cx); - if let Some(room) = call.room().cloned() { - let toggle_screen_sharing = room.update(cx, |room, cx| { - let clicked_on_currently_shared_screen = - room.shared_screen_id().is_some_and(|screen_id| { - Some(screen_id) - == screen - .as_deref() - .and_then(|s| s.metadata().ok().map(|meta| meta.id)) - }); - let should_unshare_current_screen = room.is_sharing_screen(); - let unshared_current_screen = should_unshare_current_screen.then(|| { - telemetry::event!( - "Screen Share Disabled", - room_id = room.id(), - channel_id = room.channel_id(), - ); - room.unshare_screen(clicked_on_currently_shared_screen || screen.is_none(), cx) - }); - if let Some(screen) = screen { - if !should_unshare_current_screen { + let toggle_screen_sharing = match screen { + Ok(screen) => { + let Some(room) = call.room().cloned() else { + return; + }; + + room.update(cx, |room, cx| { + let clicked_on_currently_shared_screen = + room.shared_screen_id().is_some_and(|screen_id| { + Some(screen_id) + == screen + .as_deref() + .and_then(|s| s.metadata().ok().map(|meta| meta.id)) + }); + let should_unshare_current_screen = room.is_sharing_screen(); + let unshared_current_screen = should_unshare_current_screen.then(|| { telemetry::event!( - "Screen Share Enabled", + "Screen Share Disabled", room_id = room.id(), channel_id = room.channel_id(), ); - } - cx.spawn(async move |room, cx| { - unshared_current_screen.transpose()?; - if !clicked_on_currently_shared_screen { - room.update(cx, |room, cx| room.share_screen(screen, cx))? - .await - } else { - Ok(()) + room.unshare_screen(clicked_on_currently_shared_screen || screen.is_none(), cx) + }); + if let Some(screen) = screen { + if !should_unshare_current_screen { + telemetry::event!( + "Screen Share Enabled", + room_id = room.id(), + channel_id = room.channel_id(), + ); } - }) - } else { - Task::ready(Ok(())) - } - }); - toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", window, cx, |e, _, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e))); - } + cx.spawn(async move |room, cx| { + unshared_current_screen.transpose()?; + if !clicked_on_currently_shared_screen { + room.update(cx, |room, cx| room.share_screen(screen, cx))? + .await + } else { + Ok(()) + } + }) + } else { + Task::ready(Ok(())) + } + }) + } + Err(e) => Task::ready(Err(e)), + }; + toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", window, cx, |e, _, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e))); } fn toggle_mute(_: &ToggleMute, cx: &mut App) { @@ -183,7 +189,7 @@ impl TitleBar { .as_ref()? .read(cx) .is_being_followed(collaborator.peer_id); - let is_present = project_id.map_or(false, |project_id| { + let is_present = project_id.is_some_and(|project_id| { collaborator.location == ParticipantLocation::SharedProject { project_id } }); @@ -483,9 +489,8 @@ impl TitleBar { let screen = if should_share { cx.update(|_, cx| pick_default_screen(cx))?.await } else { - None + Ok(None) }; - cx.update(|window, cx| toggle_screen_sharing(screen, window, cx))?; Result::<_, anyhow::Error>::Ok(()) @@ -571,7 +576,7 @@ impl TitleBar { selectable: true, documentation_aside: None, handler: Rc::new(move |_, window, cx| { - toggle_screen_sharing(Some(screen.clone()), window, cx); + toggle_screen_sharing(Ok(Some(screen.clone())), window, cx); }), }); } @@ -585,18 +590,18 @@ impl TitleBar { } /// Picks the screen to share when clicking on the main screen sharing button. -fn pick_default_screen(cx: &App) -> Task<Option<Rc<dyn ScreenCaptureSource>>> { +fn pick_default_screen(cx: &App) -> Task<anyhow::Result<Option<Rc<dyn ScreenCaptureSource>>>> { let source = cx.screen_capture_sources(); cx.spawn(async move |_| { - let available_sources = maybe!(async move { source.await? }).await.ok()?; - available_sources + let available_sources = source.await??; + Ok(available_sources .iter() .find(|it| { it.as_ref() .metadata() .is_ok_and(|meta| meta.is_main.unwrap_or_default()) }) - .or_else(|| available_sources.iter().next()) - .cloned() + .or_else(|| available_sources.first()) + .cloned()) }) } diff --git a/crates/title_bar/src/onboarding_banner.rs b/crates/title_bar/src/onboarding_banner.rs index e7cf0cd2d9..ed43c5277a 100644 --- a/crates/title_bar/src/onboarding_banner.rs +++ b/crates/title_bar/src/onboarding_banner.rs @@ -73,7 +73,7 @@ fn get_dismissed(source: &str) -> bool { db::kvp::KEY_VALUE_STORE .read_kvp(&dismissed_at) .log_err() - .map_or(false, |dismissed| dismissed.is_some()) + .is_some_and(|dismissed| dismissed.is_some()) } fn persist_dismissed(source: &str, cx: &mut App) { diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index eb317a5616..5bd6a17e4b 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -478,7 +478,7 @@ impl TitleBar { repo.branch .as_ref() .map(|branch| branch.name()) - .map(|name| util::truncate_and_trailoff(&name, MAX_BRANCH_NAME_LENGTH)) + .map(|name| util::truncate_and_trailoff(name, MAX_BRANCH_NAME_LENGTH)) .or_else(|| { repo.head_commit.as_ref().map(|commit| { commit @@ -593,11 +593,11 @@ impl TitleBar { Button::new("connection-status", label) .label_size(LabelSize::Small) .on_click(|_, window, cx| { - if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) { - if auto_updater.read(cx).status().is_updated() { - workspace::reload(cx); - return; - } + if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) + && auto_updater.read(cx).status().is_updated() + { + workspace::reload(cx); + return; } auto_update::check(&Default::default(), window, cx); }) @@ -617,7 +617,7 @@ impl TitleBar { window .spawn(cx, async move |cx| { client - .sign_in_with_optional_connect(true, &cx) + .sign_in_with_optional_connect(true, cx) .await .notify_async_err(cx); }) diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs index 01bd7b0a9c..ea5dcc2a19 100644 --- a/crates/toolchain_selector/src/active_toolchain.rs +++ b/crates/toolchain_selector/src/active_toolchain.rs @@ -121,31 +121,30 @@ impl ActiveToolchain { cx: &mut Context<Self>, ) { let editor = editor.read(cx); - if let Some((_, buffer, _)) = editor.active_excerpt(cx) { - if let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx)) { - if self - .active_buffer - .as_ref() - .is_some_and(|(old_worktree_id, old_buffer, _)| { - (old_worktree_id, old_buffer.entity_id()) - == (&worktree_id, buffer.entity_id()) - }) - { - return; - } - - let subscription = cx.subscribe_in( - &buffer, - window, - |this, _, event: &BufferEvent, window, cx| { - if matches!(event, BufferEvent::LanguageChanged) { - this._update_toolchain_task = Self::spawn_tracker_task(window, cx); - } - }, - ); - self.active_buffer = Some((worktree_id, buffer.downgrade(), subscription)); - self._update_toolchain_task = Self::spawn_tracker_task(window, cx); + if let Some((_, buffer, _)) = editor.active_excerpt(cx) + && let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx)) + { + if self + .active_buffer + .as_ref() + .is_some_and(|(old_worktree_id, old_buffer, _)| { + (old_worktree_id, old_buffer.entity_id()) == (&worktree_id, buffer.entity_id()) + }) + { + return; } + + let subscription = cx.subscribe_in( + &buffer, + window, + |this, _, event: &BufferEvent, window, cx| { + if matches!(event, BufferEvent::LanguageChanged) { + this._update_toolchain_task = Self::spawn_tracker_task(window, cx); + } + }, + ); + self.active_buffer = Some((worktree_id, buffer.downgrade(), subscription)); + self._update_toolchain_task = Self::spawn_tracker_task(window, cx); } cx.notify(); diff --git a/crates/toolchain_selector/src/toolchain_selector.rs b/crates/toolchain_selector/src/toolchain_selector.rs index 21d95a66de..cdd3db99e0 100644 --- a/crates/toolchain_selector/src/toolchain_selector.rs +++ b/crates/toolchain_selector/src/toolchain_selector.rs @@ -211,16 +211,15 @@ impl ToolchainSelectorDelegate { let _ = this.update_in(cx, move |this, window, cx| { this.delegate.candidates = available_toolchains; - if let Some(active_toolchain) = active_toolchain { - if let Some(position) = this + if let Some(active_toolchain) = active_toolchain + && let Some(position) = this .delegate .candidates .toolchains .iter() .position(|toolchain| *toolchain == active_toolchain) - { - this.delegate.set_selected_index(position, window, cx); - } + { + this.delegate.set_selected_index(position, window, cx); } this.update_matches(this.query(cx), window, cx); }); diff --git a/crates/ui/src/components/banner.rs b/crates/ui/src/components/banner.rs index d493e8a0d3..7458ad8eb0 100644 --- a/crates/ui/src/components/banner.rs +++ b/crates/ui/src/components/banner.rs @@ -1,15 +1,6 @@ use crate::prelude::*; use gpui::{AnyElement, IntoElement, ParentElement, Styled}; -/// Severity levels that determine the style of the banner. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Severity { - Info, - Success, - Warning, - Error, -} - /// Banners provide informative and brief messages without interrupting the user. /// This component offers four severity levels that can be used depending on the message. /// diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 0b30007e44..31bf76e843 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -400,6 +400,7 @@ pub struct ButtonLike { size: ButtonSize, rounding: Option<ButtonLikeRounding>, tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>, + hoverable_tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>, cursor_style: CursorStyle, on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>, on_right_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>, @@ -420,6 +421,7 @@ impl ButtonLike { size: ButtonSize::Default, rounding: Some(ButtonLikeRounding::All), tooltip: None, + hoverable_tooltip: None, children: SmallVec::new(), cursor_style: CursorStyle::PointingHand, on_click: None, @@ -463,6 +465,14 @@ impl ButtonLike { self.on_right_click = Some(Box::new(handler)); self } + + pub fn hoverable_tooltip( + mut self, + tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static, + ) -> Self { + self.hoverable_tooltip = Some(Box::new(tooltip)); + self + } } impl Disableable for ButtonLike { @@ -654,6 +664,9 @@ impl RenderOnce for ButtonLike { .when_some(self.tooltip, |this, tooltip| { this.tooltip(move |window, cx| tooltip(window, cx)) }) + .when_some(self.hoverable_tooltip, |this, tooltip| { + this.hoverable_tooltip(move |window, cx| tooltip(window, cx)) + }) .children(self.children) } } diff --git a/crates/ui/src/components/callout.rs b/crates/ui/src/components/callout.rs index abb03198ab..22ba0468cd 100644 --- a/crates/ui/src/components/callout.rs +++ b/crates/ui/src/components/callout.rs @@ -1,7 +1,13 @@ -use gpui::{AnyElement, Hsla}; +use gpui::AnyElement; use crate::prelude::*; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BorderPosition { + Top, + Bottom, +} + /// A callout component for displaying important information that requires user attention. /// /// # Usage Example @@ -10,42 +16,48 @@ use crate::prelude::*; /// use ui::{Callout}; /// /// Callout::new() -/// .icon(Icon::new(IconName::Warning).color(Color::Warning)) +/// .severity(Severity::Warning) +/// .icon(IconName::Warning) /// .title(Label::new("Be aware of your subscription!")) /// .description(Label::new("Your subscription is about to expire. Renew now!")) -/// .primary_action(Button::new("renew", "Renew Now")) -/// .secondary_action(Button::new("remind", "Remind Me Later")) +/// .actions_slot(Button::new("renew", "Renew Now")) /// ``` /// #[derive(IntoElement, RegisterComponent)] pub struct Callout { - icon: Option<Icon>, + severity: Severity, + icon: Option<IconName>, title: Option<SharedString>, description: Option<SharedString>, - primary_action: Option<AnyElement>, - secondary_action: Option<AnyElement>, - tertiary_action: Option<AnyElement>, + actions_slot: Option<AnyElement>, + dismiss_action: Option<AnyElement>, line_height: Option<Pixels>, - bg_color: Option<Hsla>, + border_position: BorderPosition, } impl Callout { /// Creates a new `Callout` component with default styling. pub fn new() -> Self { Self { + severity: Severity::Info, icon: None, title: None, description: None, - primary_action: None, - secondary_action: None, - tertiary_action: None, + actions_slot: None, + dismiss_action: None, line_height: None, - bg_color: None, + border_position: BorderPosition::Top, } } + /// Sets the severity of the callout. + pub fn severity(mut self, severity: Severity) -> Self { + self.severity = severity; + self + } + /// Sets the icon to display in the callout. - pub fn icon(mut self, icon: Icon) -> Self { + pub fn icon(mut self, icon: IconName) -> Self { self.icon = Some(icon); self } @@ -64,20 +76,14 @@ impl Callout { } /// Sets the primary call-to-action button. - pub fn primary_action(mut self, action: impl IntoElement) -> Self { - self.primary_action = Some(action.into_any_element()); - self - } - - /// Sets an optional secondary call-to-action button. - pub fn secondary_action(mut self, action: impl IntoElement) -> Self { - self.secondary_action = Some(action.into_any_element()); + pub fn actions_slot(mut self, action: impl IntoElement) -> Self { + self.actions_slot = Some(action.into_any_element()); self } /// Sets an optional tertiary call-to-action button. - pub fn tertiary_action(mut self, action: impl IntoElement) -> Self { - self.tertiary_action = Some(action.into_any_element()); + pub fn dismiss_action(mut self, action: impl IntoElement) -> Self { + self.dismiss_action = Some(action.into_any_element()); self } @@ -87,9 +93,9 @@ impl Callout { self } - /// Sets a custom background color for the callout content. - pub fn bg_color(mut self, color: Hsla) -> Self { - self.bg_color = Some(color); + /// Sets the border position in the callout. + pub fn border_position(mut self, border_position: BorderPosition) -> Self { + self.border_position = border_position; self } } @@ -97,21 +103,51 @@ impl Callout { impl RenderOnce for Callout { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let line_height = self.line_height.unwrap_or(window.line_height()); - let bg_color = self - .bg_color - .unwrap_or(cx.theme().colors().panel_background); - let has_actions = self.primary_action.is_some() - || self.secondary_action.is_some() - || self.tertiary_action.is_some(); + + let has_actions = self.actions_slot.is_some() || self.dismiss_action.is_some(); + + let (icon, icon_color, bg_color) = match self.severity { + Severity::Info => ( + IconName::Info, + Color::Muted, + cx.theme().colors().panel_background.opacity(0.), + ), + Severity::Success => ( + IconName::Check, + Color::Success, + cx.theme().status().success.opacity(0.1), + ), + Severity::Warning => ( + IconName::Warning, + Color::Warning, + cx.theme().status().warning_background.opacity(0.2), + ), + Severity::Error => ( + IconName::XCircle, + Color::Error, + cx.theme().status().error.opacity(0.08), + ), + }; h_flex() + .min_w_0() .p_2() .gap_2() .items_start() + .map(|this| match self.border_position { + BorderPosition::Top => this.border_t_1(), + BorderPosition::Bottom => this.border_b_1(), + }) + .border_color(cx.theme().colors().border) .bg(bg_color) .overflow_x_hidden() - .when_some(self.icon, |this, icon| { - this.child(h_flex().h(line_height).justify_center().child(icon)) + .when(self.icon.is_some(), |this| { + this.child( + h_flex() + .h(line_height) + .justify_center() + .child(Icon::new(icon).size(IconSize::Small).color(icon_color)), + ) }) .child( v_flex() @@ -119,10 +155,11 @@ impl RenderOnce for Callout { .w_full() .child( h_flex() - .h(line_height) + .min_h(line_height) .w_full() .gap_1() .justify_between() + .flex_wrap() .when_some(self.title, |this, title| { this.child(h_flex().child(Label::new(title).size(LabelSize::Small))) }) @@ -130,13 +167,10 @@ impl RenderOnce for Callout { this.child( h_flex() .gap_0p5() - .when_some(self.tertiary_action, |this, action| { + .when_some(self.actions_slot, |this, action| { this.child(action) }) - .when_some(self.secondary_action, |this, action| { - this.child(action) - }) - .when_some(self.primary_action, |this, action| { + .when_some(self.dismiss_action, |this, action| { this.child(action) }), ) @@ -168,84 +202,101 @@ impl Component for Callout { } fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> { - let callout_examples = vec![ + let single_action = || Button::new("got-it", "Got it").label_size(LabelSize::Small); + let multiple_actions = || { + h_flex() + .gap_0p5() + .child(Button::new("update", "Backup & Update").label_size(LabelSize::Small)) + .child(Button::new("dismiss", "Dismiss").label_size(LabelSize::Small)) + }; + + let basic_examples = vec![ single_example( "Simple with Title Only", Callout::new() - .icon( - Icon::new(IconName::Info) - .color(Color::Accent) - .size(IconSize::Small), - ) + .icon(IconName::Info) .title("System maintenance scheduled for tonight") - .primary_action(Button::new("got-it", "Got it").label_size(LabelSize::Small)) + .actions_slot(single_action()) .into_any_element(), ) .width(px(580.)), single_example( "With Title and Description", Callout::new() - .icon( - Icon::new(IconName::Warning) - .color(Color::Warning) - .size(IconSize::Small), - ) + .icon(IconName::Warning) .title("Your settings contain deprecated values") .description( "We'll backup your current settings and update them to the new format.", ) - .primary_action( - Button::new("update", "Backup & Update").label_size(LabelSize::Small), - ) - .secondary_action( - Button::new("dismiss", "Dismiss").label_size(LabelSize::Small), - ) + .actions_slot(single_action()) .into_any_element(), ) .width(px(580.)), single_example( "Error with Multiple Actions", Callout::new() - .icon( - Icon::new(IconName::Close) - .color(Color::Error) - .size(IconSize::Small), - ) + .icon(IconName::Close) .title("Thread reached the token limit") .description("Start a new thread from a summary to continue the conversation.") - .primary_action( - Button::new("new-thread", "Start New Thread").label_size(LabelSize::Small), - ) - .secondary_action( - Button::new("view-summary", "View Summary").label_size(LabelSize::Small), - ) + .actions_slot(multiple_actions()) .into_any_element(), ) .width(px(580.)), single_example( "Multi-line Description", Callout::new() - .icon( - Icon::new(IconName::Sparkle) - .color(Color::Accent) - .size(IconSize::Small), - ) + .icon(IconName::Sparkle) .title("Upgrade to Pro") .description("• Unlimited threads\n• Priority support\n• Advanced analytics") - .primary_action( - Button::new("upgrade", "Upgrade Now").label_size(LabelSize::Small), - ) - .secondary_action( - Button::new("learn-more", "Learn More").label_size(LabelSize::Small), - ) + .actions_slot(multiple_actions()) .into_any_element(), ) .width(px(580.)), ]; + let severity_examples = vec![ + single_example( + "Info", + Callout::new() + .icon(IconName::Info) + .title("System maintenance scheduled for tonight") + .actions_slot(single_action()) + .into_any_element(), + ), + single_example( + "Warning", + Callout::new() + .severity(Severity::Warning) + .icon(IconName::Triangle) + .title("System maintenance scheduled for tonight") + .actions_slot(single_action()) + .into_any_element(), + ), + single_example( + "Error", + Callout::new() + .severity(Severity::Error) + .icon(IconName::XCircle) + .title("System maintenance scheduled for tonight") + .actions_slot(single_action()) + .into_any_element(), + ), + single_example( + "Success", + Callout::new() + .severity(Severity::Success) + .icon(IconName::Check) + .title("System maintenance scheduled for tonight") + .actions_slot(single_action()) + .into_any_element(), + ), + ]; + Some( - example_group(callout_examples) - .vertical() + v_flex() + .gap_4() + .child(example_group(basic_examples).vertical()) + .child(example_group_with_title("Severity", severity_examples).vertical()) .into_any_element(), ) } diff --git a/crates/ui/src/components/facepile.rs b/crates/ui/src/components/facepile.rs index 879bfce041..83e99df7c2 100644 --- a/crates/ui/src/components/facepile.rs +++ b/crates/ui/src/components/facepile.rs @@ -78,7 +78,7 @@ impl RenderOnce for Facepile { } } -pub const EXAMPLE_FACES: [&'static str; 6] = [ +pub const EXAMPLE_FACES: [&str; 6] = [ "https://avatars.githubusercontent.com/u/326587?s=60&v=4", "https://avatars.githubusercontent.com/u/2280405?s=60&v=4", "https://avatars.githubusercontent.com/u/1789?s=60&v=4", diff --git a/crates/ui/src/components/indent_guides.rs b/crates/ui/src/components/indent_guides.rs index e3dc1f35fa..5e6f4ee8ba 100644 --- a/crates/ui/src/components/indent_guides.rs +++ b/crates/ui/src/components/indent_guides.rs @@ -216,7 +216,7 @@ mod uniform_list { }; let visible_entries = &compute_indents_fn(visible_range.clone(), window, cx); let indent_guides = compute_indent_guides( - &visible_entries, + visible_entries, visible_range.start, includes_trailing_indent, ); @@ -241,7 +241,7 @@ mod sticky_items { window: &mut Window, cx: &mut App, ) -> AnyElement { - let indent_guides = compute_indent_guides(&indents, 0, false); + let indent_guides = compute_indent_guides(indents, 0, false); self.render_from_layout(indent_guides, bounds, item_height, window, cx) } } diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 56be867796..bbce6101f4 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -163,7 +163,7 @@ pub fn render_keystroke( let size = size.into(); if use_text { - let element = Key::new(keystroke_text(&keystroke, platform_style, vim_mode), color) + let element = Key::new(keystroke_text(keystroke, platform_style, vim_mode), color) .size(size) .into_any_element(); vec![element] @@ -176,7 +176,7 @@ pub fn render_keystroke( size, true, )); - elements.push(render_key(&keystroke, color, platform_style, size)); + elements.push(render_key(keystroke, color, platform_style, size)); elements } } diff --git a/crates/ui/src/components/label/highlighted_label.rs b/crates/ui/src/components/label/highlighted_label.rs index 72f54e08da..576d47eda5 100644 --- a/crates/ui/src/components/label/highlighted_label.rs +++ b/crates/ui/src/components/label/highlighted_label.rs @@ -98,12 +98,12 @@ pub fn highlight_ranges( loop { end_ix = end_ix + text[end_ix..].chars().next().unwrap().len_utf8(); - if let Some(&next_ix) = highlight_indices.peek() { - if next_ix == end_ix { - end_ix = next_ix; - highlight_indices.next(); - continue; - } + if let Some(&next_ix) = highlight_indices.peek() + && next_ix == end_ix + { + end_ix = next_ix; + highlight_indices.next(); + continue; } break; } diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 55ce0218c7..439b53f038 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -72,10 +72,10 @@ impl<M: ManagedView> PopoverMenuHandle<M> { } pub fn hide(&self, cx: &mut App) { - if let Some(state) = self.0.borrow().as_ref() { - if let Some(menu) = state.menu.borrow().as_ref() { - menu.update(cx, |_, cx| cx.emit(DismissEvent)); - } + if let Some(state) = self.0.borrow().as_ref() + && let Some(menu) = state.menu.borrow().as_ref() + { + menu.update(cx, |_, cx| cx.emit(DismissEvent)); } } @@ -93,16 +93,16 @@ impl<M: ManagedView> PopoverMenuHandle<M> { self.0 .borrow() .as_ref() - .map_or(false, |state| state.menu.borrow().as_ref().is_some()) + .is_some_and(|state| state.menu.borrow().as_ref().is_some()) } pub fn is_focused(&self, window: &Window, cx: &App) -> bool { - self.0.borrow().as_ref().map_or(false, |state| { + self.0.borrow().as_ref().is_some_and(|state| { state .menu .borrow() .as_ref() - .map_or(false, |model| model.focus_handle(cx).is_focused(window)) + .is_some_and(|model| model.focus_handle(cx).is_focused(window)) }) } @@ -278,10 +278,10 @@ fn show_menu<M: ManagedView>( window .subscribe(&new_menu, cx, move |modal, _: &DismissEvent, window, cx| { - if modal.focus_handle(cx).contains_focused(window, cx) { - if let Some(previous_focus_handle) = previous_focus_handle.as_ref() { - window.focus(previous_focus_handle); - } + if modal.focus_handle(cx).contains_focused(window, cx) + && let Some(previous_focus_handle) = previous_focus_handle.as_ref() + { + window.focus(previous_focus_handle); } *menu2.borrow_mut() = None; window.refresh(); @@ -373,14 +373,14 @@ impl<M: ManagedView> Element for PopoverMenu<M> { (child_builder)(element_state.menu.clone(), self.menu_builder.clone()) }); - if let Some(trigger_handle) = self.trigger_handle.take() { - if let Some(menu_builder) = self.menu_builder.clone() { - *trigger_handle.0.borrow_mut() = Some(PopoverMenuHandleState { - menu_builder, - menu: element_state.menu.clone(), - on_open: self.on_open.clone(), - }); - } + if let Some(trigger_handle) = self.trigger_handle.take() + && let Some(menu_builder) = self.menu_builder.clone() + { + *trigger_handle.0.borrow_mut() = Some(PopoverMenuHandleState { + menu_builder, + menu: element_state.menu.clone(), + on_open: self.on_open.clone(), + }); } let child_layout_id = child_element diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 85ef549bc0..761189671b 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -250,12 +250,11 @@ impl<M: ManagedView> Element for RightClickMenu<M> { window .subscribe(&new_menu, cx, move |modal, _: &DismissEvent, window, cx| { - if modal.focus_handle(cx).contains_focused(window, cx) { - if let Some(previous_focus_handle) = + if modal.focus_handle(cx).contains_focused(window, cx) + && let Some(previous_focus_handle) = previous_focus_handle.as_ref() - { - window.focus(previous_focus_handle); - } + { + window.focus(previous_focus_handle); } *menu2.borrow_mut() = None; window.refresh(); diff --git a/crates/ui/src/components/sticky_items.rs b/crates/ui/src/components/sticky_items.rs index ca8b336a5a..c3e0886404 100644 --- a/crates/ui/src/components/sticky_items.rs +++ b/crates/ui/src/components/sticky_items.rs @@ -105,7 +105,6 @@ impl Element for StickyItemsElement { _window: &mut Window, _cx: &mut App, ) -> Self::PrepaintState { - () } fn paint( diff --git a/crates/ui/src/components/toggle.rs b/crates/ui/src/components/toggle.rs index e5f28e3b25..2ca635c05b 100644 --- a/crates/ui/src/components/toggle.rs +++ b/crates/ui/src/components/toggle.rs @@ -616,7 +616,7 @@ impl SwitchField { Self { id: id.into(), label: label.into(), - description: description, + description, toggle_state: toggle_state.into(), on_click: Arc::new(on_click), disabled: false, diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index ed0fdd0114..65ed2f2b68 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/crates/ui/src/components/tooltip.rs @@ -175,7 +175,7 @@ impl Tooltip { move |_, cx| { let title = title.clone(); cx.new(|_| Self { - title: title, + title, meta: None, key_binding: None, }) diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 80f8f863f8..0357e498bb 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -14,7 +14,9 @@ pub use ui_macros::RegisterComponent; pub use crate::DynamicSpacing; pub use crate::animation::{AnimationDirection, AnimationDuration, DefaultAnimations}; -pub use crate::styles::{PlatformStyle, StyledTypography, TextSize, rems_from_px, vh, vw}; +pub use crate::styles::{ + PlatformStyle, Severity, StyledTypography, TextSize, rems_from_px, vh, vw, +}; pub use crate::traits::clickable::*; pub use crate::traits::disableable::*; pub use crate::traits::fixed::*; diff --git a/crates/ui/src/styles.rs b/crates/ui/src/styles.rs index af6ab57029..bc2399f54b 100644 --- a/crates/ui/src/styles.rs +++ b/crates/ui/src/styles.rs @@ -3,6 +3,7 @@ mod appearance; mod color; mod elevation; mod platform; +mod severity; mod spacing; mod typography; mod units; @@ -11,6 +12,7 @@ pub use appearance::*; pub use color::*; pub use elevation::*; pub use platform::*; +pub use severity::*; pub use spacing::*; pub use typography::*; pub use units::*; diff --git a/crates/ui/src/styles/severity.rs b/crates/ui/src/styles/severity.rs new file mode 100644 index 0000000000..464f835186 --- /dev/null +++ b/crates/ui/src/styles/severity.rs @@ -0,0 +1,10 @@ +/// Severity levels that determine the style of the component. +/// Usually, it affects the background. Most of the time, +/// it also follows with an icon corresponding the severity level. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Severity { + Info, + Success, + Warning, + Error, +} diff --git a/crates/util/src/fs.rs b/crates/util/src/fs.rs index 3e96594f85..60aab4a2e7 100644 --- a/crates/util/src/fs.rs +++ b/crates/util/src/fs.rs @@ -13,13 +13,13 @@ where while let Some(entry) = entries.next().await { if let Some(entry) = entry.log_err() { let entry_path = entry.path(); - if predicate(entry_path.as_path()) { - if let Ok(metadata) = fs::metadata(&entry_path).await { - if metadata.is_file() { - fs::remove_file(&entry_path).await.log_err(); - } else { - fs::remove_dir_all(&entry_path).await.log_err(); - } + if predicate(entry_path.as_path()) + && let Ok(metadata) = fs::metadata(&entry_path).await + { + if metadata.is_file() { + fs::remove_file(&entry_path).await.log_err(); + } else { + fs::remove_dir_all(&entry_path).await.log_err(); } } } @@ -35,10 +35,10 @@ where if let Some(mut entries) = fs::read_dir(dir).await.log_err() { while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - if predicate(entry.path().as_path()) { - matching.push(entry.path()); - } + if let Some(entry) = entry.log_err() + && predicate(entry.path().as_path()) + { + matching.push(entry.path()); } } } @@ -58,10 +58,9 @@ where if let Some(file_name) = entry_path .file_name() .map(|file_name| file_name.to_string_lossy()) + && predicate(&file_name) { - if predicate(&file_name) { - return Some(entry_path); - } + return Some(entry_path); } } } diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 211831125d..292ec4874c 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -1215,11 +1215,11 @@ mod tests { // Verify iterators advanced correctly assert!( - !a_iter.next().map_or(false, |c| c.is_ascii_digit()), + !a_iter.next().is_some_and(|c| c.is_ascii_digit()), "Iterator a should have consumed all digits" ); assert!( - !b_iter.next().map_or(false, |c| c.is_ascii_digit()), + !b_iter.next().is_some_and(|c| c.is_ascii_digit()), "Iterator b should have consumed all digits" ); diff --git a/crates/util/src/schemars.rs b/crates/util/src/schemars.rs index e162b41933..a59d24c325 100644 --- a/crates/util/src/schemars.rs +++ b/crates/util/src/schemars.rs @@ -44,13 +44,12 @@ pub struct DefaultDenyUnknownFields; impl schemars::transform::Transform for DefaultDenyUnknownFields { fn transform(&mut self, schema: &mut schemars::Schema) { - if let Some(object) = schema.as_object_mut() { - if object.contains_key("properties") - && !object.contains_key("additionalProperties") - && !object.contains_key("unevaluatedProperties") - { - object.insert("additionalProperties".to_string(), false.into()); - } + if let Some(object) = schema.as_object_mut() + && object.contains_key("properties") + && !object.contains_key("additionalProperties") + && !object.contains_key("unevaluatedProperties") + { + object.insert("additionalProperties".to_string(), false.into()); } transform_subschemas(self, schema); } diff --git a/crates/util/src/size.rs b/crates/util/src/size.rs index 084a0e5a56..c6ecebd548 100644 --- a/crates/util/src/size.rs +++ b/crates/util/src/size.rs @@ -7,14 +7,12 @@ pub fn format_file_size(size: u64, use_decimal: bool) -> String { } else { format!("{:.1}MB", size as f64 / (1000.0 * 1000.0)) } + } else if size < 1024 { + format!("{size}B") + } else if size < 1024 * 1024 { + format!("{:.1}KiB", size as f64 / 1024.0) } else { - if size < 1024 { - format!("{size}B") - } else if size < 1024 * 1024 { - format!("{:.1}KiB", size as f64 / 1024.0) - } else { - format!("{:.1}MiB", size as f64 / (1024.0 * 1024.0)) - } + format!("{:.1}MiB", size as f64 / (1024.0 * 1024.0)) } } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index e1b25f4dba..69a2c88706 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -128,11 +128,9 @@ pub fn truncate_lines_to_byte_limit(s: &str, max_bytes: usize) -> &str { } for i in (0..max_bytes).rev() { - if s.is_char_boundary(i) { - if s.as_bytes()[i] == b'\n' { - // Since the i-th character is \n, valid to slice at i + 1. - return &s[..i + 1]; - } + if s.is_char_boundary(i) && s.as_bytes()[i] == b'\n' { + // Since the i-th character is \n, valid to slice at i + 1. + return &s[..i + 1]; } } @@ -303,7 +301,7 @@ pub fn get_shell_safe_zed_path() -> anyhow::Result<String> { let zed_path_escaped = shlex::try_quote(&zed_path).context("Failed to shell-escape Zed executable path.")?; - return Ok(zed_path_escaped.to_string()); + Ok(zed_path_escaped.to_string()) } #[cfg(unix)] @@ -827,7 +825,7 @@ mod rng { pub fn new(rng: T) -> Self { Self { rng, - simple_text: std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()), + simple_text: std::env::var("SIMPLE_TEXT").is_ok_and(|v| !v.is_empty()), } } diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index ce5e5a0300..7269fc8bec 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -95,7 +95,7 @@ impl VimOption { } } - Self::possibilities(&prefix) + Self::possibilities(prefix) .map(|possible| { let mut options = prefix_of_options.clone(); options.push(possible); @@ -510,17 +510,16 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) { vim.switch_mode(Mode::Normal, true, window, cx); } vim.update_editor(cx, |_, editor, cx| { - if let Some(first_sel) = initial_selections { - if let Some(tx_id) = editor + if let Some(first_sel) = initial_selections + && let Some(tx_id) = editor .buffer() .update(cx, |multi, cx| multi.last_transaction_id(cx)) - { - let last_sel = editor.selections.disjoint_anchors(); - editor.modify_transaction_selection_history(tx_id, |old| { - old.0 = first_sel; - old.1 = Some(last_sel); - }); - } + { + let last_sel = editor.selections.disjoint_anchors(); + editor.modify_transaction_selection_history(tx_id, |old| { + old.0 = first_sel; + old.1 = Some(last_sel); + }); } }); }) @@ -567,7 +566,6 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) { workspace.update(cx, |workspace, cx| { e.notify_err(workspace, cx); }); - return; } }); @@ -1445,7 +1443,7 @@ pub fn command_interceptor(mut input: &str, cx: &App) -> Vec<CommandInterceptRes }]; } } - return Vec::default(); + Vec::default() } fn generate_positions(string: &str, query: &str) -> Vec<usize> { @@ -1713,14 +1711,12 @@ impl Vim { match c { '%' => { self.update_editor(cx, |_, editor, cx| { - if let Some((_, buffer, _)) = editor.active_excerpt(cx) { - if let Some(file) = buffer.read(cx).file() { - if let Some(local) = file.as_local() { - if let Some(str) = local.path().to_str() { - ret.push_str(str) - } - } - } + if let Some((_, buffer, _)) = editor.active_excerpt(cx) + && let Some(file) = buffer.read(cx).file() + && let Some(local) = file.as_local() + && let Some(str) = local.path().to_str() + { + ret.push_str(str) } }); } @@ -1954,19 +1950,19 @@ impl ShellExec { return; }; - if let Some(mut stdin) = running.stdin.take() { - if let Some(snapshot) = input_snapshot { - let range = range.clone(); - cx.background_spawn(async move { - for chunk in snapshot.text_for_range(range) { - if stdin.write_all(chunk.as_bytes()).log_err().is_none() { - return; - } + if let Some(mut stdin) = running.stdin.take() + && let Some(snapshot) = input_snapshot + { + let range = range.clone(); + cx.background_spawn(async move { + for chunk in snapshot.text_for_range(range) { + if stdin.write_all(chunk.as_bytes()).log_err().is_none() { + return; } - stdin.flush().log_err(); - }) - .detach(); - } + } + stdin.flush().log_err(); + }) + .detach(); }; let output = cx diff --git a/crates/vim/src/digraph.rs b/crates/vim/src/digraph.rs index c555b781b1..248047bb55 100644 --- a/crates/vim/src/digraph.rs +++ b/crates/vim/src/digraph.rs @@ -63,15 +63,15 @@ impl Vim { } fn literal(&mut self, action: &Literal, window: &mut Window, cx: &mut Context<Self>) { - if let Some(Operator::Literal { prefix }) = self.active_operator() { - if let Some(prefix) = prefix { - if let Some(keystroke) = Keystroke::parse(&action.0).ok() { - window.defer(cx, |window, cx| { - window.dispatch_keystroke(keystroke, cx); - }); - } - return self.handle_literal_input(prefix, "", window, cx); + if let Some(Operator::Literal { prefix }) = self.active_operator() + && let Some(prefix) = prefix + { + if let Some(keystroke) = Keystroke::parse(&action.0).ok() { + window.defer(cx, |window, cx| { + window.dispatch_keystroke(keystroke, cx); + }); } + return self.handle_literal_input(prefix, "", window, cx); } self.insert_literal(Some(action.1), "", window, cx); @@ -103,7 +103,6 @@ impl Vim { window.dispatch_keystroke(keystroke, cx); }); } - return; } pub fn handle_literal_input( diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 0c8c06d8ab..2bc531268d 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -47,7 +47,6 @@ impl Vim { } self.stop_recording_immediately(action.boxed_clone(), cx); self.switch_mode(Mode::HelixNormal, false, window, cx); - return; } pub fn helix_normal_motion( @@ -202,10 +201,7 @@ impl Vim { let right_kind = classifier.kind_with(right, ignore_punctuation); let at_newline = (left == '\n') ^ (right == '\n'); - let found = (left_kind != right_kind && right_kind != CharKind::Whitespace) - || at_newline; - - found + (left_kind != right_kind && right_kind != CharKind::Whitespace) || at_newline }) } Motion::NextWordEnd { ignore_punctuation } => { @@ -214,10 +210,7 @@ impl Vim { let right_kind = classifier.kind_with(right, ignore_punctuation); let at_newline = (left == '\n') ^ (right == '\n'); - let found = (left_kind != right_kind && left_kind != CharKind::Whitespace) - || at_newline; - - found + (left_kind != right_kind && left_kind != CharKind::Whitespace) || at_newline }) } Motion::PreviousWordStart { ignore_punctuation } => { @@ -226,10 +219,7 @@ impl Vim { let right_kind = classifier.kind_with(right, ignore_punctuation); let at_newline = (left == '\n') ^ (right == '\n'); - let found = (left_kind != right_kind && left_kind != CharKind::Whitespace) - || at_newline; - - found + (left_kind != right_kind && left_kind != CharKind::Whitespace) || at_newline }) } Motion::PreviousWordEnd { ignore_punctuation } => { @@ -238,10 +228,7 @@ impl Vim { let right_kind = classifier.kind_with(right, ignore_punctuation); let at_newline = (left == '\n') ^ (right == '\n'); - let found = (left_kind != right_kind && right_kind != CharKind::Whitespace) - || at_newline; - - found + (left_kind != right_kind && right_kind != CharKind::Whitespace) || at_newline }) } Motion::FindForward { @@ -547,27 +534,27 @@ mod test { ); } - // #[gpui::test] - // async fn test_delete_character_end_of_line(cx: &mut gpui::TestAppContext) { - // let mut cx = VimTestContext::new(cx, true).await; + #[gpui::test] + async fn test_delete_character_end_of_line(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; - // cx.set_state( - // indoc! {" - // The quick brownˇ - // fox jumps over - // the lazy dog."}, - // Mode::HelixNormal, - // ); + cx.set_state( + indoc! {" + The quick brownˇ + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); - // cx.simulate_keystrokes("d"); + cx.simulate_keystrokes("d"); - // cx.assert_state( - // indoc! {" - // The quick brownˇfox jumps over - // the lazy dog."}, - // Mode::HelixNormal, - // ); - // } + cx.assert_state( + indoc! {" + The quick brownˇfox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + } // #[gpui::test] // async fn test_delete_character_end_of_buffer(cx: &mut gpui::TestAppContext) { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index a6a07e7b2f..92e3c97265 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1811,10 +1811,10 @@ fn previous_word_end( .ignore_punctuation(ignore_punctuation); let mut point = point.to_point(map); - if point.column < map.buffer_snapshot.line_len(MultiBufferRow(point.row)) { - if let Some(ch) = map.buffer_snapshot.chars_at(point).next() { - point.column += ch.len_utf8() as u32; - } + if point.column < map.buffer_snapshot.line_len(MultiBufferRow(point.row)) + && let Some(ch) = map.buffer_snapshot.chars_at(point).next() + { + point.column += ch.len_utf8() as u32; } for _ in 0..times { let new_point = movement::find_preceding_boundary_point( @@ -1986,10 +1986,10 @@ fn previous_subword_end( .ignore_punctuation(ignore_punctuation); let mut point = point.to_point(map); - if point.column < map.buffer_snapshot.line_len(MultiBufferRow(point.row)) { - if let Some(ch) = map.buffer_snapshot.chars_at(point).next() { - point.column += ch.len_utf8() as u32; - } + if point.column < map.buffer_snapshot.line_len(MultiBufferRow(point.row)) + && let Some(ch) = map.buffer_snapshot.chars_at(point).next() + { + point.column += ch.len_utf8() as u32; } for _ in 0..times { let new_point = movement::find_preceding_boundary_point( @@ -2054,10 +2054,10 @@ pub(crate) fn last_non_whitespace( let classifier = map.buffer_snapshot.char_classifier_at(from.to_point(map)); // NOTE: depending on clip_at_line_end we may already be one char back from the end. - if let Some((ch, _)) = map.buffer_chars_at(end_of_line).next() { - if classifier.kind(ch) != CharKind::Whitespace { - return end_of_line.to_display_point(map); - } + if let Some((ch, _)) = map.buffer_chars_at(end_of_line).next() + && classifier.kind(ch) != CharKind::Whitespace + { + return end_of_line.to_display_point(map); } for (ch, offset) in map.reverse_buffer_chars_at(end_of_line) { @@ -2280,8 +2280,8 @@ fn go_to_line(map: &DisplaySnapshot, display_point: DisplayPoint, line: usize) - } let mut last_position = None; for (excerpt, buffer, range) in map.buffer_snapshot.excerpts() { - let excerpt_range = language::ToOffset::to_offset(&range.context.start, &buffer) - ..language::ToOffset::to_offset(&range.context.end, &buffer); + let excerpt_range = language::ToOffset::to_offset(&range.context.start, buffer) + ..language::ToOffset::to_offset(&range.context.end, buffer); if offset >= excerpt_range.start && offset <= excerpt_range.end { let text_anchor = buffer.anchor_after(offset); let anchor = Anchor::in_buffer(excerpt, buffer.remote_id(), text_anchor); @@ -2375,7 +2375,7 @@ fn matching_tag(map: &DisplaySnapshot, head: DisplayPoint) -> Option<DisplayPoin } } - return None; + None } fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { @@ -2517,7 +2517,7 @@ fn unmatched_forward( } display_point = new_point; } - return display_point; + display_point } fn unmatched_backward( @@ -2882,7 +2882,7 @@ fn method_motion( } else { possibilities.min().unwrap_or(offset) }; - let new_point = map.clip_point(dest.to_display_point(&map), Bias::Left); + let new_point = map.clip_point(dest.to_display_point(map), Bias::Left); if new_point == display_point { break; } @@ -2936,7 +2936,7 @@ fn comment_motion( } else { possibilities.min().unwrap_or(offset) }; - let new_point = map.clip_point(dest.to_display_point(&map), Bias::Left); + let new_point = map.clip_point(dest.to_display_point(map), Bias::Left); if new_point == display_point { break; } @@ -3003,7 +3003,7 @@ fn section_motion( possibilities.min().unwrap_or(map.buffer_snapshot.len()) }; - let new_point = map.clip_point(offset.to_display_point(&map), Bias::Left); + let new_point = map.clip_point(offset.to_display_point(map), Bias::Left); if new_point == display_point { break; } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index b74d85b7c5..0c7b6e55a1 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -221,7 +221,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) { return; }; - let anchors = last_change.iter().cloned().collect::<Vec<_>>(); + let anchors = last_change.to_vec(); let mut last_row = None; let ranges: Vec<_> = anchors .iter() diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index fcd36dd7ee..2af22bf050 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -155,12 +155,11 @@ fn expand_changed_word_selection( let classifier = map .buffer_snapshot .char_classifier_at(selection.start.to_point(map)); - let in_word = map - .buffer_chars_at(selection.head().to_offset(map, Bias::Left)) + + map.buffer_chars_at(selection.head().to_offset(map, Bias::Left)) .next() .map(|(c, _)| !classifier.is_whitespace(c)) - .unwrap_or_default(); - in_word + .unwrap_or_default() }; if (times.is_none() || times.unwrap() == 1) && is_in_word() { let next_char = map diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index 1b7557371a..6f406d0c44 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -2,6 +2,7 @@ use crate::{ Vim, motion::{Motion, MotionKind}, object::Object, + state::Mode, }; use collections::{HashMap, HashSet}; use editor::{ @@ -73,10 +74,10 @@ impl Vim { editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head(); - if kind.linewise() { - if let Some(column) = original_columns.get(&selection.id) { - *cursor.column_mut() = *column - } + if kind.linewise() + && let Some(column) = original_columns.get(&selection.id) + { + *cursor.column_mut() = *column } cursor = map.clip_point(cursor, Bias::Left); selection.collapse_to(cursor, selection.goal) @@ -102,8 +103,20 @@ impl Vim { // Emulates behavior in vim where if we expanded backwards to include a newline // the cursor gets set back to the start of the line let mut should_move_to_start: HashSet<_> = Default::default(); + + // Emulates behavior in vim where after deletion the cursor should try to move + // to the same column it was before deletion if the line is not empty or only + // contains whitespace + let mut column_before_move: HashMap<_, _> = Default::default(); + let target_mode = object.target_visual_mode(vim.mode, around); + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { + let cursor_point = selection.head().to_point(map); + if target_mode == Mode::VisualLine { + column_before_move.insert(selection.id, cursor_point.column); + } + object.expand_selection(map, selection, around, times); let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range(); let mut move_selection_start_to_previous_line = @@ -164,6 +177,15 @@ impl Vim { let mut cursor = selection.head(); if should_move_to_start.contains(&selection.id) { *cursor.column_mut() = 0; + } else if let Some(column) = column_before_move.get(&selection.id) + && *column > 0 + { + let mut cursor_point = cursor.to_point(map); + cursor_point.column = *column; + cursor = map + .buffer_snapshot + .clip_point(cursor_point, Bias::Left) + .to_display_point(map); } cursor = map.clip_point(cursor, Bias::Left); selection.collapse_to(cursor, selection.goal) diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index 007514e472..115aef1dab 100644 --- a/crates/vim/src/normal/increment.rs +++ b/crates/vim/src/normal/increment.rs @@ -155,7 +155,7 @@ fn increment_decimal_string(num: &str, delta: i64) -> String { } fn increment_hex_string(num: &str, delta: i64) -> String { - let result = if let Ok(val) = u64::from_str_radix(&num, 16) { + let result = if let Ok(val) = u64::from_str_radix(num, 16) { val.wrapping_add_signed(delta) } else { u64::MAX @@ -181,7 +181,7 @@ fn should_use_lowercase(num: &str) -> bool { } fn increment_binary_string(num: &str, delta: i64) -> String { - let result = if let Ok(val) = u64::from_str_radix(&num, 2) { + let result = if let Ok(val) = u64::from_str_radix(num, 2) { val.wrapping_add_signed(delta) } else { u64::MAX diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index 1d6264d593..619769d41a 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -120,7 +120,6 @@ impl Vim { }); }) }); - return; } fn open_path_mark( @@ -256,10 +255,8 @@ impl Vim { } }); - if should_jump { - if let Some(anchor) = anchor { - self.motion(Motion::Jump { anchor, line }, window, cx) - } + if should_jump && let Some(anchor) = anchor { + self.motion(Motion::Jump { anchor, line }, window, cx) } } } diff --git a/crates/vim/src/normal/repeat.rs b/crates/vim/src/normal/repeat.rs index 5cc3762990..2d79274808 100644 --- a/crates/vim/src/normal/repeat.rs +++ b/crates/vim/src/normal/repeat.rs @@ -221,14 +221,14 @@ impl Vim { if actions.is_empty() { return None; } - if globals.replayer.is_none() { - if let Some(recording_register) = globals.recording_register { - globals - .recordings - .entry(recording_register) - .or_default() - .push(ReplayableAction::Action(Repeat.boxed_clone())); - } + if globals.replayer.is_none() + && let Some(recording_register) = globals.recording_register + { + globals + .recordings + .entry(recording_register) + .or_default() + .push(ReplayableAction::Action(Repeat.boxed_clone())); } let mut mode = None; @@ -320,10 +320,10 @@ impl Vim { // vim doesn't treat 3a1 as though you literally repeated a1 // 3 times, instead it inserts the content thrice at the insert position. if let Some(to_repeat) = repeatable_insert(&actions[0]) { - if let Some(ReplayableAction::Action(action)) = actions.last() { - if NormalBefore.partial_eq(&**action) { - actions.pop(); - } + if let Some(ReplayableAction::Action(action)) = actions.last() + && NormalBefore.partial_eq(&**action) + { + actions.pop(); } let mut new_actions = actions.clone(); diff --git a/crates/vim/src/normal/scroll.rs b/crates/vim/src/normal/scroll.rs index af13bc0fd0..9eb8367f57 100644 --- a/crates/vim/src/normal/scroll.rs +++ b/crates/vim/src/normal/scroll.rs @@ -549,7 +549,7 @@ mod test { cx.set_neovim_option("nowrap").await; let content = "ˇ01234567890123456789"; - cx.set_shared_state(&content).await; + cx.set_shared_state(content).await; cx.simulate_shared_keystrokes("z shift-l").await; cx.shared_state().await.assert_eq("012345ˇ67890123456789"); @@ -560,7 +560,7 @@ mod test { cx.shared_state().await.assert_eq("012345ˇ67890123456789"); let content = "ˇ01234567890123456789"; - cx.set_shared_state(&content).await; + cx.set_shared_state(content).await; cx.simulate_shared_keystrokes("z l").await; cx.shared_state().await.assert_eq("0ˇ1234567890123456789"); diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 4054c552ae..4fbeec7236 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -224,7 +224,7 @@ impl Vim { .search .prior_selections .last() - .map_or(true, |range| range.start != new_head); + .is_none_or(|range| range.start != new_head); if is_different_head { count = count.saturating_sub(1) diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 63139d7e94..c65da4f90b 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -100,10 +100,10 @@ fn cover_or_next<I: Iterator<Item = (Range<usize>, Range<usize>)>>( for (open_range, close_range) in ranges { let start_off = open_range.start; let end_off = close_range.end; - if let Some(range_filter) = range_filter { - if !range_filter(open_range.clone(), close_range.clone()) { - continue; - } + if let Some(range_filter) = range_filter + && !range_filter(open_range.clone(), close_range.clone()) + { + continue; } let candidate = CandidateWithRanges { candidate: CandidateRange { @@ -1060,11 +1060,11 @@ fn text_object( .filter_map(|(r, m)| if m == target { Some(r) } else { None }) .collect(); matches.sort_by_key(|r| r.start); - if let Some(buffer_range) = matches.first() { - if !buffer_range.is_empty() { - let range = excerpt.map_range_from_buffer(buffer_range.clone()); - return Some(range.start.to_display_point(map)..range.end.to_display_point(map)); - } + if let Some(buffer_range) = matches.first() + && !buffer_range.is_empty() + { + let range = excerpt.map_range_from_buffer(buffer_range.clone()); + return Some(range.start.to_display_point(map)..range.end.to_display_point(map)); } let buffer_range = excerpt.map_range_from_buffer(around_range.clone()); return Some(buffer_range.start.to_display_point(map)..buffer_range.end.to_display_point(map)); @@ -1444,14 +1444,15 @@ fn paragraph( return None; } - let paragraph_start_row = paragraph_start.row(); - if paragraph_start_row.0 != 0 { + let paragraph_start_buffer_point = paragraph_start.to_point(map); + if paragraph_start_buffer_point.row != 0 { let previous_paragraph_last_line_start = - Point::new(paragraph_start_row.0 - 1, 0).to_display_point(map); + Point::new(paragraph_start_buffer_point.row - 1, 0).to_display_point(map); paragraph_start = start_of_paragraph(map, previous_paragraph_last_line_start); } } else { - let mut start_row = paragraph_end_row.0 + 1; + let paragraph_end_buffer_point = paragraph_end.to_point(map); + let mut start_row = paragraph_end_buffer_point.row + 1; if i > 0 { start_row += 1; } @@ -1528,25 +1529,25 @@ fn surrounding_markers( Some((ch, _)) => ch, _ => '\0', }; - if let Some((ch, range)) = movement::chars_after(map, point).next() { - if ch == open_marker && before_ch != '\\' { - if open_marker == close_marker { - let mut total = 0; - for ((ch, _), (before_ch, _)) in movement::chars_before(map, point).tuple_windows() - { - if ch == '\n' { - break; - } - if ch == open_marker && before_ch != '\\' { - total += 1; - } + if let Some((ch, range)) = movement::chars_after(map, point).next() + && ch == open_marker + && before_ch != '\\' + { + if open_marker == close_marker { + let mut total = 0; + for ((ch, _), (before_ch, _)) in movement::chars_before(map, point).tuple_windows() { + if ch == '\n' { + break; } - if total % 2 == 0 { - opening = Some(range) + if ch == open_marker && before_ch != '\\' { + total += 1; } - } else { + } + if total % 2 == 0 { opening = Some(range) } + } else { + opening = Some(range) } } @@ -1557,10 +1558,10 @@ fn surrounding_markers( break; } - if let Some((before_ch, _)) = chars_before.peek() { - if *before_ch == '\\' { - continue; - } + if let Some((before_ch, _)) = chars_before.peek() + && *before_ch == '\\' + { + continue; } if ch == open_marker { @@ -1903,6 +1904,90 @@ mod test { } } + #[gpui::test] + async fn test_change_paragraph_object_with_soft_wrap(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + const WRAPPING_EXAMPLE: &str = indoc! {" + ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines. + + ˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly. + + ˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.ˇ + "}; + + cx.set_shared_wrap(20).await; + + cx.simulate_at_each_offset("c i p", WRAPPING_EXAMPLE) + .await + .assert_matches(); + cx.simulate_at_each_offset("c a p", WRAPPING_EXAMPLE) + .await + .assert_matches(); + } + + #[gpui::test] + async fn test_delete_paragraph_object_with_soft_wrap(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + const WRAPPING_EXAMPLE: &str = indoc! {" + ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines. + + ˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly. + + ˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.ˇ + "}; + + cx.set_shared_wrap(20).await; + + cx.simulate_at_each_offset("d i p", WRAPPING_EXAMPLE) + .await + .assert_matches(); + cx.simulate_at_each_offset("d a p", WRAPPING_EXAMPLE) + .await + .assert_matches(); + } + + #[gpui::test] + async fn test_delete_paragraph_whitespace(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! {" + a + ˇ• + aaaaaaaaaaaaa + "}) + .await; + + cx.simulate_shared_keystrokes("d i p").await; + cx.shared_state().await.assert_eq(indoc! {" + a + aaaaaaaˇaaaaaa + "}); + } + + #[gpui::test] + async fn test_visual_paragraph_object_with_soft_wrap(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + const WRAPPING_EXAMPLE: &str = indoc! {" + ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines. + + ˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly. + + ˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.ˇ + "}; + + cx.set_shared_wrap(20).await; + + cx.simulate_at_each_offset("v i p", WRAPPING_EXAMPLE) + .await + .assert_matches(); + cx.simulate_at_each_offset("v a p", WRAPPING_EXAMPLE) + .await + .assert_matches(); + } + // Test string with "`" for opening surrounders and "'" for closing surrounders const SURROUNDING_MARKER_STRING: &str = indoc! {" ˇTh'ˇe ˇ`ˇ'ˇquˇi`ˇck broˇ'wn` diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index c4be034871..23efd39139 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -255,16 +255,11 @@ impl MarksState { pub fn new(workspace: &Workspace, cx: &mut App) -> Entity<MarksState> { cx.new(|cx| { let buffer_store = workspace.project().read(cx).buffer_store().clone(); - let subscription = - cx.subscribe( - &buffer_store, - move |this: &mut Self, _, event, cx| match event { - project::buffer_store::BufferStoreEvent::BufferAdded(buffer) => { - this.on_buffer_loaded(buffer, cx); - } - _ => {} - }, - ); + let subscription = cx.subscribe(&buffer_store, move |this: &mut Self, _, event, cx| { + if let project::buffer_store::BufferStoreEvent::BufferAdded(buffer) = event { + this.on_buffer_loaded(buffer, cx); + } + }); let mut this = Self { workspace: workspace.weak_handle(), @@ -412,20 +407,20 @@ impl MarksState { let mut to_write = HashMap::default(); for (key, value) in &new_points { - if self.is_global_mark(key) { - if self.global_marks.get(key) != Some(&MarkLocation::Path(path.clone())) { - if let Some(workspace_id) = self.workspace_id(cx) { - let path = path.clone(); - let key = key.clone(); - cx.background_spawn(async move { - DB.set_global_mark_path(workspace_id, key, path).await - }) - .detach_and_log_err(cx); - } - - self.global_marks - .insert(key.clone(), MarkLocation::Path(path.clone())); + if self.is_global_mark(key) + && self.global_marks.get(key) != Some(&MarkLocation::Path(path.clone())) + { + if let Some(workspace_id) = self.workspace_id(cx) { + let path = path.clone(); + let key = key.clone(); + cx.background_spawn(async move { + DB.set_global_mark_path(workspace_id, key, path).await + }) + .detach_and_log_err(cx); } + + self.global_marks + .insert(key.clone(), MarkLocation::Path(path.clone())); } if old_points.and_then(|o| o.get(key)) != Some(value) { to_write.insert(key.clone(), value.clone()); @@ -456,15 +451,15 @@ impl MarksState { buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) { - if let MarkLocation::Buffer(entity_id) = old_path { - if let Some(old_marks) = self.multibuffer_marks.remove(&entity_id) { - let buffer_marks = old_marks - .into_iter() - .map(|(k, v)| (k, v.into_iter().map(|anchor| anchor.text_anchor).collect())) - .collect(); - self.buffer_marks - .insert(buffer.read(cx).remote_id(), buffer_marks); - } + if let MarkLocation::Buffer(entity_id) = old_path + && let Some(old_marks) = self.multibuffer_marks.remove(&entity_id) + { + let buffer_marks = old_marks + .into_iter() + .map(|(k, v)| (k, v.into_iter().map(|anchor| anchor.text_anchor).collect())) + .collect(); + self.buffer_marks + .insert(buffer.read(cx).remote_id(), buffer_marks); } self.watch_buffer(MarkLocation::Path(new_path.clone()), buffer, cx); self.serialize_buffer_marks(new_path, buffer, cx); @@ -512,10 +507,9 @@ impl MarksState { .watched_buffers .get(&buffer_id.clone()) .map(|(path, _, _)| path.clone()) + && let Some(new_path) = this.path_for_buffer(&buffer, cx) { - if let Some(new_path) = this.path_for_buffer(&buffer, cx) { - this.rename_buffer(old_path, new_path, &buffer, cx) - } + this.rename_buffer(old_path, new_path, &buffer, cx) } } _ => {} @@ -540,7 +534,7 @@ impl MarksState { cx: &mut Context<Self>, ) { let buffer = multibuffer.read(cx).as_singleton(); - let abs_path = buffer.as_ref().and_then(|b| self.path_for_buffer(&b, cx)); + let abs_path = buffer.as_ref().and_then(|b| self.path_for_buffer(b, cx)); let Some(abs_path) = abs_path else { self.multibuffer_marks @@ -597,7 +591,7 @@ impl MarksState { if let Some(anchors) = self.buffer_marks.get(&buffer_id) { let text_anchors = anchors.get(name)?; let anchors = text_anchors - .into_iter() + .iter() .map(|anchor| Anchor::in_buffer(excerpt_id, buffer_id, *anchor)) .collect(); return Some(Mark::Local(anchors)); @@ -606,12 +600,12 @@ impl MarksState { match target? { MarkLocation::Buffer(entity_id) => { - let anchors = self.multibuffer_marks.get(&entity_id)?; - return Some(Mark::Buffer(*entity_id, anchors.get(name)?.clone())); + let anchors = self.multibuffer_marks.get(entity_id)?; + Some(Mark::Buffer(*entity_id, anchors.get(name)?.clone())) } MarkLocation::Path(path) => { let points = self.serialized_marks.get(path)?; - return Some(Mark::Path(path.clone(), points.get(name)?.clone())); + Some(Mark::Path(path.clone(), points.get(name)?.clone())) } } } @@ -636,7 +630,7 @@ impl MarksState { match target { MarkLocation::Buffer(entity_id) => { self.multibuffer_marks - .get_mut(&entity_id) + .get_mut(entity_id) .map(|m| m.remove(&mark_name.clone())); return; } @@ -897,13 +891,13 @@ impl VimGlobals { self.stop_recording_after_next_action = false; } } - if self.replayer.is_none() { - if let Some(recording_register) = self.recording_register { - self.recordings - .entry(recording_register) - .or_default() - .push(ReplayableAction::Action(action)); - } + if self.replayer.is_none() + && let Some(recording_register) = self.recording_register + { + self.recordings + .entry(recording_register) + .or_default() + .push(ReplayableAction::Action(action)); } } @@ -1028,13 +1022,21 @@ impl Operator { } pub fn status(&self) -> String { + fn make_visible(c: &str) -> &str { + match c { + "\n" => "enter", + "\t" => "tab", + " " => "space", + c => c, + } + } match self { Operator::Digraph { first_char: Some(first_char), - } => format!("^K{first_char}"), + } => format!("^K{}", make_visible(&first_char.to_string())), Operator::Literal { prefix: Some(prefix), - } => format!("^V{prefix}"), + } => format!("^V{}", make_visible(prefix)), Operator::AutoIndent => "=".to_string(), Operator::ShellCommand => "=".to_string(), _ => self.id().to_string(), @@ -1322,10 +1324,10 @@ impl MarksMatchInfo { let mut offset = 0; for chunk in chunks { line.push_str(chunk.text); - if let Some(highlight_style) = chunk.syntax_highlight_id { - if let Some(highlight) = highlight_style.style(cx.theme().syntax()) { - highlights.push((offset..offset + chunk.text.len(), highlight)) - } + if let Some(highlight_style) = chunk.syntax_highlight_id + && let Some(highlight) = highlight_style.style(cx.theme().syntax()) + { + highlights.push((offset..offset + chunk.text.len(), highlight)) } offset += chunk.text.len(); } @@ -1703,26 +1705,25 @@ impl VimDb { marks: HashMap<String, Vec<Point>>, ) -> Result<()> { log::debug!("Setting path {path:?} for {} marks", marks.len()); - let result = self - .write(move |conn| { - let mut query = conn.exec_bound(sql!( - INSERT OR REPLACE INTO vim_marks - (workspace_id, mark_name, path, value) - VALUES - (?, ?, ?, ?) - ))?; - for (mark_name, value) in marks { - let pairs: Vec<(u32, u32)> = value - .into_iter() - .map(|point| (point.row, point.column)) - .collect(); - let serialized = serde_json::to_string(&pairs)?; - query((workspace_id, mark_name, path.clone(), serialized))?; - } - Ok(()) - }) - .await; - result + + self.write(move |conn| { + let mut query = conn.exec_bound(sql!( + INSERT OR REPLACE INTO vim_marks + (workspace_id, mark_name, path, value) + VALUES + (?, ?, ?, ?) + ))?; + for (mark_name, value) in marks { + let pairs: Vec<(u32, u32)> = value + .into_iter() + .map(|point| (point.row, point.column)) + .collect(); + let serialized = serde_json::to_string(&pairs)?; + query((workspace_id, mark_name, path.clone(), serialized))?; + } + Ok(()) + }) + .await } fn get_marks(&self, workspace_id: WorkspaceId) -> Result<Vec<SerializedMark>> { diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 63cd21e88c..ca65204fab 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -174,12 +174,11 @@ impl Vim { if ch.to_string() == pair.start { let start = offset; let mut end = start + 1; - if surround { - if let Some((next_ch, _)) = chars_and_offset.peek() { - if next_ch.eq(&' ') { - end += 1; - } - } + if surround + && let Some((next_ch, _)) = chars_and_offset.peek() + && next_ch.eq(&' ') + { + end += 1; } edits.push((start..end, "")); anchors.push(start..start); @@ -193,12 +192,11 @@ impl Vim { if ch.to_string() == pair.end { let mut start = offset; let end = start + 1; - if surround { - if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() { - if next_ch.eq(&' ') { - start -= 1; - } - } + if surround + && let Some((next_ch, _)) = reverse_chars_and_offsets.peek() + && next_ch.eq(&' ') + { + start -= 1; } edits.push((start..end, "")); break; diff --git a/crates/vim/src/test/neovim_connection.rs b/crates/vim/src/test/neovim_connection.rs index 46ea261cd6..f87ccc283f 100644 --- a/crates/vim/src/test/neovim_connection.rs +++ b/crates/vim/src/test/neovim_connection.rs @@ -67,7 +67,7 @@ impl NeovimConnection { // Ensure we don't create neovim connections in parallel let _lock = NEOVIM_LOCK.lock(); let (nvim, join_handle, child) = new_child_cmd( - &mut Command::new("nvim") + Command::new("nvim") .arg("--embed") .arg("--clean") // disable swap (otherwise after about 1000 test runs you run out of swap file names) @@ -161,7 +161,7 @@ impl NeovimConnection { #[cfg(feature = "neovim")] pub async fn set_state(&mut self, marked_text: &str) { - let (text, selections) = parse_state(&marked_text); + let (text, selections) = parse_state(marked_text); let nvim_buffer = self .nvim @@ -217,10 +217,11 @@ impl NeovimConnection { .expect("Could not set nvim cursor position"); } - if let Some(NeovimData::Get { mode, state }) = self.data.back() { - if *mode == Mode::Normal && *state == marked_text { - return; - } + if let Some(NeovimData::Get { mode, state }) = self.data.back() + && *mode == Mode::Normal + && *state == marked_text + { + return; } self.data.push_back(NeovimData::Put { state: marked_text.to_string(), @@ -298,10 +299,10 @@ impl NeovimConnection { if let Some(NeovimData::Get { .. }) = self.data.front() { self.data.pop_front(); }; - if let Some(NeovimData::ReadRegister { name, value }) = self.data.pop_front() { - if name == register { - return value; - } + if let Some(NeovimData::ReadRegister { name, value }) = self.data.pop_front() + && name == register + { + return value; } panic!("operation does not match recorded script. re-record with --features=neovim") @@ -589,7 +590,7 @@ fn parse_state(marked_text: &str) -> (String, Vec<Range<Point>>) { #[cfg(feature = "neovim")] fn encode_ranges(text: &str, point_ranges: &Vec<Range<Point>>) -> String { let byte_ranges = point_ranges - .into_iter() + .iter() .map(|range| { let mut byte_range = 0..0; let mut ix = 0; diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 44d9b8f456..81c1a6b0b3 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -265,7 +265,7 @@ pub fn init(cx: &mut App) { workspace.register_action(|workspace, _: &MaximizePane, window, cx| { let pane = workspace.active_pane(); - let Some(size) = workspace.bounding_box_for_pane(&pane) else { + let Some(size) = workspace.bounding_box_for_pane(pane) else { return; }; @@ -788,10 +788,10 @@ impl Vim { editor.selections.line_mode = false; editor.unregister_addon::<VimAddon>(); editor.set_relative_line_number(None, cx); - if let Some(vim) = Vim::globals(cx).focused_vim() { - if vim.entity_id() == cx.entity().entity_id() { - Vim::globals(cx).focused_vim = None; - } + if let Some(vim) = Vim::globals(cx).focused_vim() + && vim.entity_id() == cx.entity().entity_id() + { + Vim::globals(cx).focused_vim = None; } } @@ -833,10 +833,10 @@ impl Vim { if self.exit_temporary_mode { self.exit_temporary_mode = false; // Don't switch to insert mode if the action is temporary_normal. - if let Some(action) = keystroke_event.action.as_ref() { - if action.as_any().downcast_ref::<TemporaryNormal>().is_some() { - return; - } + if let Some(action) = keystroke_event.action.as_ref() + && action.as_any().downcast_ref::<TemporaryNormal>().is_some() + { + return; } self.switch_mode(Mode::Insert, false, window, cx) } @@ -1006,10 +1006,10 @@ impl Vim { Some((point, goal)) }) } - if last_mode == Mode::Insert || last_mode == Mode::Replace { - if let Some(prior_tx) = prior_tx { - editor.group_until_transaction(prior_tx, cx) - } + if (last_mode == Mode::Insert || last_mode == Mode::Replace) + && let Some(prior_tx) = prior_tx + { + editor.group_until_transaction(prior_tx, cx) } editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { @@ -1031,14 +1031,16 @@ impl Vim { } let snapshot = s.display_map(); - if let Some(pending) = s.pending.as_mut() { - if pending.selection.reversed && mode.is_visual() && !last_mode.is_visual() { - let mut end = pending.selection.end.to_point(&snapshot.buffer_snapshot); - end = snapshot - .buffer_snapshot - .clip_point(end + Point::new(0, 1), Bias::Right); - pending.selection.end = snapshot.buffer_snapshot.anchor_before(end); - } + if let Some(pending) = s.pending.as_mut() + && pending.selection.reversed + && mode.is_visual() + && !last_mode.is_visual() + { + let mut end = pending.selection.end.to_point(&snapshot.buffer_snapshot); + end = snapshot + .buffer_snapshot + .clip_point(end + Point::new(0, 1), Bias::Right); + pending.selection.end = snapshot.buffer_snapshot.anchor_before(end); } s.move_with(|map, selection| { @@ -1536,12 +1538,12 @@ impl Vim { if self.mode == Mode::Insert && self.current_tx.is_some() { if self.current_anchor.is_none() { self.current_anchor = Some(newest); - } else if self.current_anchor.as_ref().unwrap() != &newest { - if let Some(tx_id) = self.current_tx.take() { - self.update_editor(cx, |_, editor, cx| { - editor.group_until_transaction(tx_id, cx) - }); - } + } else if self.current_anchor.as_ref().unwrap() != &newest + && let Some(tx_id) = self.current_tx.take() + { + self.update_editor(cx, |_, editor, cx| { + editor.group_until_transaction(tx_id, cx) + }); } } else if self.mode == Mode::Normal && newest.start != newest.end { if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) { @@ -1599,7 +1601,7 @@ impl Vim { second_char, smartcase: VimSettings::get_global(cx).use_smartcase_find, }; - Vim::globals(cx).last_find = Some((&sneak).clone()); + Vim::globals(cx).last_find = Some(sneak.clone()); self.motion(sneak, window, cx) } } else { @@ -1616,7 +1618,7 @@ impl Vim { second_char, smartcase: VimSettings::get_global(cx).use_smartcase_find, }; - Vim::globals(cx).last_find = Some((&sneak).clone()); + Vim::globals(cx).last_find = Some(sneak.clone()); self.motion(sneak, window, cx) } } else { diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 7bfd8dc8be..ffbae3ff76 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -414,6 +414,8 @@ impl Vim { ); } + let original_point = selection.tail().to_point(map); + if let Some(range) = object.range(map, mut_selection, around, count) { if !range.is_empty() { let expand_both_ways = object.always_expands_both_ways() @@ -462,6 +464,37 @@ impl Vim { }; selection.end = new_selection_end.to_display_point(map); } + + // To match vim, if the range starts of the same line as it originally + // did, we keep the tail of the selection in the same place instead of + // snapping it to the start of the line + if target_mode == Mode::VisualLine { + let new_start_point = selection.start.to_point(map); + if new_start_point.row == original_point.row { + if selection.end.to_point(map).row > new_start_point.row { + if original_point.column + == map + .buffer_snapshot + .line_len(MultiBufferRow(original_point.row)) + { + selection.start = movement::saturating_left( + map, + original_point.to_display_point(map), + ) + } else { + selection.start = original_point.to_display_point(map) + } + } else { + selection.end = movement::saturating_right( + map, + original_point.to_display_point(map), + ); + if original_point.column > 0 { + selection.reversed = true + } + } + } + } } }); }); diff --git a/crates/vim/test_data/test_change_paragraph_object_with_soft_wrap.json b/crates/vim/test_data/test_change_paragraph_object_with_soft_wrap.json new file mode 100644 index 0000000000..47d68e13a6 --- /dev/null +++ b/crates/vim/test_data/test_change_paragraph_object_with_soft_wrap.json @@ -0,0 +1,72 @@ +{"SetOption":{"value":"wrap"}} +{"SetOption":{"value":"columns=20"}} +{"Put":{"state":"ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇ\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇ\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.ˇ\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ\n","mode":"Insert"}} +{"Put":{"state":"ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇ\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇ\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ","mode":"Insert"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.ˇ\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ","mode":"Insert"}} diff --git a/crates/vim/test_data/test_delete_paragraph_object_with_soft_wrap.json b/crates/vim/test_data/test_delete_paragraph_object_with_soft_wrap.json new file mode 100644 index 0000000000..19dcd175b3 --- /dev/null +++ b/crates/vim/test_data/test_delete_paragraph_object_with_soft_wrap.json @@ -0,0 +1,72 @@ +{"SetOption":{"value":"wrap"}} +{"SetOption":{"value":"columns=20"}} +{"Put":{"state":"ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇ\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇ\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.ˇ\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ","mode":"Normal"}} +{"Put":{"state":"ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"Second paragraph that is also quite long and will definitely wrap under soft wrap conditions andˇ should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nThird paragraph with additional long text content that will also wrap when line length is constraˇined by the wrapping settings.\n","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\nˇ","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\nˇ","mode":"Normal"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.ˇ\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\nˇ","mode":"Normal"}} diff --git a/crates/vim/test_data/test_delete_paragraph_whitespace.json b/crates/vim/test_data/test_delete_paragraph_whitespace.json new file mode 100644 index 0000000000..e07b18eaa3 --- /dev/null +++ b/crates/vim/test_data/test_delete_paragraph_whitespace.json @@ -0,0 +1,5 @@ +{"Put":{"state":"a\n ˇ•\naaaaaaaaaaaaa\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"a\naaaaaaaˇaaaaaa\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_visual_paragraph_object_with_soft_wrap.json b/crates/vim/test_data/test_visual_paragraph_object_with_soft_wrap.json new file mode 100644 index 0000000000..6bfce2f955 --- /dev/null +++ b/crates/vim/test_data/test_visual_paragraph_object_with_soft_wrap.json @@ -0,0 +1,72 @@ +{"SetOption":{"value":"wrap"}} +{"SetOption":{"value":"columns=20"}} +{"Put":{"state":"ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"«Fˇ»irst paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"«ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is l»imited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\n«Sˇ»econd paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\n«ˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and s»hould be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\n«Tˇ»hird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\n«ˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping s»ettings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.ˇ\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\n«ˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.»\n","mode":"VisualLine"}} +{"Put":{"state":"ˇFirst paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"«First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇ»Second paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is ˇlimited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is «limited making it span multiple display lines.\n\nˇ»Second paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nˇSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\n«Second paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇ»Third paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and ˇshould be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and «should be handled correctly.\n\nˇ»Third paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nˇThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\n«Third paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.\nˇ»","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping ˇsettings.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping «settings.\nˇ»","mode":"VisualLine"}} +{"Put":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings.ˇ\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"First paragraph with very long text that will wrap when soft wrap is enabled and line length is limited making it span multiple display lines.\n\nSecond paragraph that is also quite long and will definitely wrap under soft wrap conditions and should be handled correctly.\n\nThird paragraph with additional long text content that will also wrap when line length is constrained by the wrapping settings«.\nˇ»","mode":"VisualLine"}} diff --git a/crates/web_search_providers/src/cloud.rs b/crates/web_search_providers/src/cloud.rs index 52ee0da0d4..75ffb1da63 100644 --- a/crates/web_search_providers/src/cloud.rs +++ b/crates/web_search_providers/src/cloud.rs @@ -50,7 +50,7 @@ impl State { } } -pub const ZED_WEB_SEARCH_PROVIDER_ID: &'static str = "zed.dev"; +pub const ZED_WEB_SEARCH_PROVIDER_ID: &str = "zed.dev"; impl WebSearchProvider for CloudWebSearchProvider { fn id(&self) -> WebSearchProviderId { diff --git a/crates/web_search_providers/src/web_search_providers.rs b/crates/web_search_providers/src/web_search_providers.rs index 2248cb7eb3..8ab0aee47a 100644 --- a/crates/web_search_providers/src/web_search_providers.rs +++ b/crates/web_search_providers/src/web_search_providers.rs @@ -27,11 +27,10 @@ fn register_web_search_providers( cx.subscribe( &LanguageModelRegistry::global(cx), - move |this, registry, event, cx| match event { - language_model::Event::DefaultModelChanged => { + move |this, registry, event, cx| { + if let language_model::Event::DefaultModelChanged = event { register_zed_web_search_provider(this, client.clone(), ®istry, cx) } - _ => {} }, ) .detach(); @@ -46,7 +45,7 @@ fn register_zed_web_search_provider( let using_zed_provider = language_model_registry .read(cx) .default_model() - .map_or(false, |default| default.is_provided_by_zed()); + .is_some_and(|default| default.is_provided_by_zed()); if using_zed_provider { registry.register_provider(cloud::CloudWebSearchProvider::new(client, cx), cx) } else { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index ae72df3971..1d9170684e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -305,15 +305,14 @@ impl Dock { .detach(); cx.observe_in(&dock, window, move |workspace, dock, window, cx| { - if dock.read(cx).is_open() { - if let Some(panel) = dock.read(cx).active_panel() { - if panel.is_zoomed(window, cx) { - workspace.zoomed = Some(panel.to_any().downgrade()); - workspace.zoomed_position = Some(position); - cx.emit(Event::ZoomChanged); - return; - } - } + if dock.read(cx).is_open() + && let Some(panel) = dock.read(cx).active_panel() + && panel.is_zoomed(window, cx) + { + workspace.zoomed = Some(panel.to_any().downgrade()); + workspace.zoomed_position = Some(position); + cx.emit(Event::ZoomChanged); + return; } if workspace.zoomed_position == Some(position) { workspace.zoomed = None; @@ -461,7 +460,7 @@ impl Dock { }; let was_visible = this.is_open() - && this.visible_panel().map_or(false, |active_panel| { + && this.visible_panel().is_some_and(|active_panel| { active_panel.panel_id() == Entity::entity_id(&panel) }); @@ -524,7 +523,7 @@ impl Dock { PanelEvent::Close => { if this .visible_panel() - .map_or(false, |p| p.panel_id() == Entity::entity_id(panel)) + .is_some_and(|p| p.panel_id() == Entity::entity_id(panel)) { this.set_open(false, window, cx); } @@ -541,10 +540,10 @@ impl Dock { Ok(ix) => ix, Err(ix) => ix, }; - if let Some(active_index) = self.active_panel_index.as_mut() { - if *active_index >= index { - *active_index += 1; - } + if let Some(active_index) = self.active_panel_index.as_mut() + && *active_index >= index + { + *active_index += 1; } self.panel_entries.insert( index, @@ -566,16 +565,16 @@ impl Dock { pub fn restore_state(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool { if let Some(serialized) = self.serialized_dock.clone() { - if let Some(active_panel) = serialized.active_panel.filter(|_| serialized.visible) { - if let Some(idx) = self.panel_index_for_persistent_name(active_panel.as_str(), cx) { - self.activate_panel(idx, window, cx); - } + if let Some(active_panel) = serialized.active_panel.filter(|_| serialized.visible) + && let Some(idx) = self.panel_index_for_persistent_name(active_panel.as_str(), cx) + { + self.activate_panel(idx, window, cx); } - if serialized.zoom { - if let Some(panel) = self.active_panel() { - panel.set_zoomed(true, window, cx) - } + if serialized.zoom + && let Some(panel) = self.active_panel() + { + panel.set_zoomed(true, window, cx) } self.set_open(serialized.visible, window, cx); return true; diff --git a/crates/workspace/src/history_manager.rs b/crates/workspace/src/history_manager.rs index e63b1823ea..a8387369f4 100644 --- a/crates/workspace/src/history_manager.rs +++ b/crates/workspace/src/history_manager.rs @@ -101,11 +101,11 @@ impl HistoryManager { } let mut deleted_ids = Vec::new(); for idx in (0..self.history.len()).rev() { - if let Some(entry) = self.history.get(idx) { - if user_removed.contains(&entry.path) { - deleted_ids.push(entry.id); - self.history.remove(idx); - } + if let Some(entry) = self.history.get(idx) + && user_removed.contains(&entry.path) + { + deleted_ids.push(entry.id); + self.history.remove(idx); } } cx.spawn(async move |_| { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 0c5543650e..5a497398f9 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -489,7 +489,7 @@ where fn should_serialize(&self, event: &dyn Any, cx: &App) -> bool { event .downcast_ref::<T::Event>() - .map_or(false, |event| self.read(cx).should_serialize(event)) + .is_some_and(|event| self.read(cx).should_serialize(event)) } } @@ -832,10 +832,10 @@ impl<T: Item> ItemHandle for Entity<T> { if let Some(item) = item.to_followable_item_handle(cx) { let leader_id = workspace.leader_for_pane(&pane); - if let Some(leader_id) = leader_id { - if let Some(FollowEvent::Unfollow) = item.to_follow_event(event) { - workspace.unfollow(leader_id, window, cx); - } + if let Some(leader_id) = leader_id + && let Some(FollowEvent::Unfollow) = item.to_follow_event(event) + { + workspace.unfollow(leader_id, window, cx); } if item.item_focus_handle(cx).contains_focused(window, cx) { @@ -863,10 +863,10 @@ impl<T: Item> ItemHandle for Entity<T> { } } - if let Some(item) = item.to_serializable_item_handle(cx) { - if item.should_serialize(event, cx) { - workspace.enqueue_item_serialization(item).ok(); - } + if let Some(item) = item.to_serializable_item_handle(cx) + && item.should_serialize(event, cx) + { + workspace.enqueue_item_serialization(item).ok(); } T::to_item_events(event, |event| match event { @@ -948,11 +948,11 @@ impl<T: Item> ItemHandle for Entity<T> { &self.read(cx).focus_handle(cx), window, move |workspace, window, cx| { - if let Some(item) = weak_item.upgrade() { - if item.workspace_settings(cx).autosave == AutosaveSetting::OnFocusChange { - Pane::autosave_item(&item, workspace.project.clone(), window, cx) - .detach_and_log_err(cx); - } + if let Some(item) = weak_item.upgrade() + && item.workspace_settings(cx).autosave == AutosaveSetting::OnFocusChange + { + Pane::autosave_item(&item, workspace.project.clone(), window, cx) + .detach_and_log_err(cx); } }, ) diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index 7e92c7b8e9..bcd7db3a82 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -141,10 +141,10 @@ impl ModalLayer { } if let Some(active_modal) = self.active_modal.take() { - if let Some(previous_focus) = active_modal.previous_focus_handle { - if active_modal.focus_handle.contains_focused(window, cx) { - previous_focus.focus(window); - } + if let Some(previous_focus) = active_modal.previous_focus_handle + && active_modal.focus_handle.contains_focused(window, cx) + { + previous_focus.focus(window); } cx.notify(); } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 1356322a5c..8af39be3e7 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -1038,7 +1038,7 @@ where { fn detach_and_notify_err(self, window: &mut Window, cx: &mut App) { window - .spawn(cx, async move |mut cx| self.await.notify_async_err(&mut cx)) + .spawn(cx, async move |cx| self.await.notify_async_err(cx)) .detach(); } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 860a57c21f..d42b59f08e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -552,9 +552,9 @@ impl Pane { // to the item, and `focus_handle.contains_focus` returns false because the `active_item` // is not hooked up to us in the dispatch tree. self.focus_handle.contains_focused(window, cx) - || self.active_item().map_or(false, |item| { - item.item_focus_handle(cx).contains_focused(window, cx) - }) + || self + .active_item() + .is_some_and(|item| item.item_focus_handle(cx).contains_focused(window, cx)) } fn focus_in(&mut self, window: &mut Window, cx: &mut Context<Self>) { @@ -580,19 +580,18 @@ impl Pane { // or focus the active item itself if let Some(weak_last_focus_handle) = self.last_focus_handle_by_item.get(&active_item.item_id()) + && let Some(focus_handle) = weak_last_focus_handle.upgrade() { - if let Some(focus_handle) = weak_last_focus_handle.upgrade() { - focus_handle.focus(window); - return; - } + focus_handle.focus(window); + return; } active_item.item_focus_handle(cx).focus(window); - } else if let Some(focused) = window.focused(cx) { - if !self.context_menu_focused(window, cx) { - self.last_focus_handle_by_item - .insert(active_item.item_id(), focused.downgrade()); - } + } else if let Some(focused) = window.focused(cx) + && !self.context_menu_focused(window, cx) + { + self.last_focus_handle_by_item + .insert(active_item.item_id(), focused.downgrade()); } } } @@ -858,10 +857,11 @@ impl Pane { } pub fn handle_item_edit(&mut self, item_id: EntityId, cx: &App) { - if let Some(preview_item) = self.preview_item() { - if preview_item.item_id() == item_id && !preview_item.preserve_preview(cx) { - self.set_preview_item_id(None, cx); - } + if let Some(preview_item) = self.preview_item() + && preview_item.item_id() == item_id + && !preview_item.preserve_preview(cx) + { + self.set_preview_item_id(None, cx); } } @@ -900,12 +900,12 @@ impl Pane { if let Some((index, existing_item)) = existing_item { // 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) = self.preview_item_id { - if let Some(tab) = self.items.get(index) { - if tab.item_id() == preview_item_id && !allow_preview { - self.set_preview_item_id(None, cx); - } - } + if let Some(preview_item_id) = self.preview_item_id + && let Some(tab) = self.items.get(index) + && tab.item_id() == preview_item_id + && !allow_preview + { + self.set_preview_item_id(None, cx); } if activate { self.activate_item(index, focus_item, focus_item, window, cx); @@ -977,21 +977,21 @@ impl Pane { self.close_items_on_item_open(window, cx); } - if item.is_singleton(cx) { - if let Some(&entry_id) = item.project_entry_ids(cx).first() { - let Some(project) = self.project.upgrade() else { - return; - }; + if item.is_singleton(cx) + && let Some(&entry_id) = item.project_entry_ids(cx).first() + { + let Some(project) = self.project.upgrade() else { + return; + }; - let project = project.read(cx); - if let Some(project_path) = project.path_for_entry(entry_id, cx) { - let abs_path = project.absolute_path(&project_path, cx); - self.nav_history - .0 - .lock() - .paths_by_item - .insert(item.item_id(), (project_path, abs_path)); - } + let project = project.read(cx); + if let Some(project_path) = project.path_for_entry(entry_id, cx) { + let abs_path = project.absolute_path(&project_path, cx); + self.nav_history + .0 + .lock() + .paths_by_item + .insert(item.item_id(), (project_path, abs_path)); } } // If no destination index is specified, add or move the item after the @@ -1021,7 +1021,7 @@ impl Pane { existing_item .project_entry_ids(cx) .first() - .map_or(false, |existing_entry_id| { + .is_some_and(|existing_entry_id| { Some(existing_entry_id) == project_entry_id.as_ref() }) } else { @@ -1192,12 +1192,11 @@ impl Pane { use NavigationMode::{GoingBack, GoingForward}; if index < self.items.len() { let prev_active_item_ix = mem::replace(&mut self.active_item_index, index); - if prev_active_item_ix != self.active_item_index - || matches!(self.nav_history.mode(), GoingBack | GoingForward) + if (prev_active_item_ix != self.active_item_index + || matches!(self.nav_history.mode(), GoingBack | GoingForward)) + && let Some(prev_item) = self.items.get(prev_active_item_ix) { - if let Some(prev_item) = self.items.get(prev_active_item_ix) { - prev_item.deactivated(window, cx); - } + prev_item.deactivated(window, cx); } self.update_history(index); self.update_toolbar(window, cx); @@ -1559,7 +1558,7 @@ impl Pane { let other_project_item_ids = open_item.project_item_model_ids(cx); dirty_project_item_ids.retain(|id| !other_project_item_ids.contains(id)); } - return dirty_project_item_ids.is_empty(); + dirty_project_item_ids.is_empty() } pub(super) fn file_names_for_prompt( @@ -1627,8 +1626,7 @@ impl Pane { items_to_close .iter() .filter(|item| { - item.is_dirty(cx) - && !Self::skip_save_on_close(item.as_ref(), &workspace, cx) + item.is_dirty(cx) && !Self::skip_save_on_close(item.as_ref(), workspace, cx) }) .map(|item| item.boxed_clone()) .collect::<Vec<_>>() @@ -1657,7 +1655,7 @@ impl Pane { let mut should_save = true; if save_intent == SaveIntent::Close { workspace.update(cx, |workspace, cx| { - if Self::skip_save_on_close(item_to_close.as_ref(), &workspace, cx) { + if Self::skip_save_on_close(item_to_close.as_ref(), workspace, cx) { should_save = false; } })?; @@ -2463,10 +2461,11 @@ impl Pane { .on_mouse_down( MouseButton::Left, cx.listener(move |pane, event: &MouseDownEvent, _, cx| { - if let Some(id) = pane.preview_item_id { - if id == item_id && event.click_count > 1 { - pane.set_preview_item_id(None, cx); - } + if let Some(id) = pane.preview_item_id + && id == item_id + && event.click_count > 1 + { + pane.set_preview_item_id(None, cx); } }), ) @@ -2746,7 +2745,7 @@ impl Pane { worktree .read(cx) .root_entry() - .map_or(false, |entry| entry.is_dir()) + .is_some_and(|entry| entry.is_dir()) }); let entry_abs_path = pane.read(cx).entry_abs_path(entry, cx); @@ -3049,18 +3048,18 @@ impl Pane { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(custom_drop_handle) = self.custom_drop_handle.clone() { - if let ControlFlow::Break(()) = custom_drop_handle(self, dragged_tab, window, cx) { - return; - } + if let Some(custom_drop_handle) = self.custom_drop_handle.clone() + && let ControlFlow::Break(()) = custom_drop_handle(self, dragged_tab, window, cx) + { + return; } 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 { - if item_id == preview_item_id { - self.set_preview_item_id(None, cx); - } + if let Some(preview_item_id) = self.preview_item_id + && item_id == preview_item_id + { + self.set_preview_item_id(None, cx); } let is_clone = cfg!(target_os = "macos") && window.modifiers().alt @@ -3137,11 +3136,10 @@ impl Pane { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(custom_drop_handle) = self.custom_drop_handle.clone() { - if let ControlFlow::Break(()) = custom_drop_handle(self, dragged_selection, window, cx) - { - return; - } + if let Some(custom_drop_handle) = self.custom_drop_handle.clone() + && let ControlFlow::Break(()) = custom_drop_handle(self, dragged_selection, window, cx) + { + return; } self.handle_project_entry_drop( &dragged_selection.active_selection.entry_id, @@ -3158,10 +3156,10 @@ impl Pane { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(custom_drop_handle) = self.custom_drop_handle.clone() { - if let ControlFlow::Break(()) = custom_drop_handle(self, project_entry_id, window, cx) { - return; - } + if let Some(custom_drop_handle) = self.custom_drop_handle.clone() + && let ControlFlow::Break(()) = custom_drop_handle(self, project_entry_id, window, cx) + { + return; } let mut to_pane = cx.entity(); let split_direction = self.drag_split_direction; @@ -3212,8 +3210,7 @@ impl Pane { return; }; - if target.map_or(false, |target| this.is_tab_pinned(target)) - { + if target.is_some_and(|target| this.is_tab_pinned(target)) { this.pin_tab_at(index, window, cx); } }) @@ -3234,10 +3231,10 @@ impl Pane { window: &mut Window, cx: &mut Context<Self>, ) { - if let Some(custom_drop_handle) = self.custom_drop_handle.clone() { - if let ControlFlow::Break(()) = custom_drop_handle(self, paths, window, cx) { - return; - } + if let Some(custom_drop_handle) = self.custom_drop_handle.clone() + && let ControlFlow::Break(()) = custom_drop_handle(self, paths, window, cx) + { + return; } let mut to_pane = cx.entity(); let mut split_direction = self.drag_split_direction; @@ -3617,7 +3614,6 @@ impl Render for Pane { ) .on_action(cx.listener(|_, _: &menu::Cancel, window, cx| { if cx.stop_active_drag(window) { - return; } else { cx.propagate(); } @@ -3791,10 +3787,10 @@ impl NavHistory { borrowed_history.paths_by_item.get(&entry.item.id()) { f(entry, project_and_abs_path.clone()); - } else if let Some(item) = entry.item.upgrade() { - if let Some(path) = item.project_path(cx) { - f(entry, (path, None)); - } + } else if let Some(item) = entry.item.upgrade() + && let Some(path) = item.project_path(cx) + { + f(entry, (path, None)); } }) } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 5c87206e9e..bd2aafb7f4 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -619,15 +619,15 @@ impl PaneAxis { let mut found_axis_index: Option<usize> = None; if !found_pane { for (i, pa) in self.members.iter_mut().enumerate() { - if let Member::Axis(pa) = pa { - if let Some(done) = pa.resize(pane, axis, amount, bounds) { - if done { - return Some(true); // pane found and operations already done - } else if self.axis != axis { - return Some(false); // pane found but this is not the correct axis direction - } else { - found_axis_index = Some(i); // pane found and this is correct direction - } + if let Member::Axis(pa) = pa + && let Some(done) = pa.resize(pane, axis, amount, bounds) + { + if done { + return Some(true); // pane found and operations already done + } else if self.axis != axis { + return Some(false); // pane found but this is not the correct axis direction + } else { + found_axis_index = Some(i); // pane found and this is correct direction } } } @@ -743,13 +743,13 @@ impl PaneAxis { let bounding_boxes = self.bounding_boxes.lock(); for (idx, member) in self.members.iter().enumerate() { - if let Some(coordinates) = bounding_boxes[idx] { - if coordinates.contains(&coordinate) { - return match member { - Member::Pane(found) => Some(found), - Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), - }; - } + if let Some(coordinates) = bounding_boxes[idx] + && coordinates.contains(&coordinate) + { + return match member { + Member::Pane(found) => Some(found), + Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), + }; } } None @@ -1273,17 +1273,18 @@ mod element { window.paint_quad(gpui::fill(overlay_bounds, overlay_background)); } - if let Some(border) = overlay_border { - if self.active_pane_ix == Some(ix) && child.is_leaf_pane { - window.paint_quad(gpui::quad( - overlay_bounds, - 0., - gpui::transparent_black(), - border, - cx.theme().colors().border_selected, - BorderStyle::Solid, - )); - } + if let Some(border) = overlay_border + && self.active_pane_ix == Some(ix) + && child.is_leaf_pane + { + window.paint_quad(gpui::quad( + overlay_bounds, + 0., + gpui::transparent_black(), + border, + cx.theme().colors().border_selected, + BorderStyle::Solid, + )); } } diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index febb83d683..d77be8ed76 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -33,13 +33,12 @@ impl SharedScreen { cx: &mut Context<Self>, ) -> Self { let my_sid = track.sid(); - cx.subscribe(&room, move |_, _, ev, cx| match ev { - call::room::Event::RemoteVideoTrackUnsubscribed { sid } => { - if sid == &my_sid { - cx.emit(Event::Close) - } + cx.subscribe(&room, move |_, _, ev, cx| { + if let call::room::Event::RemoteVideoTrackUnsubscribed { sid } = ev + && sid == &my_sid + { + cx.emit(Event::Close) } - _ => {} }) .detach(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1eaa125ba5..d64a4472a0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -256,11 +256,6 @@ actions!( ] ); -#[derive(Clone, PartialEq)] -pub struct OpenPaths { - pub paths: Vec<PathBuf>, -} - /// Activates a specific pane by its index. #[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)] #[action(namespace = workspace)] @@ -561,6 +556,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) { files: true, directories: true, multiple: true, + prompt: None, }, cx, ); @@ -578,6 +574,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) { files: true, directories, multiple: true, + prompt: None, }, cx, ); @@ -645,7 +642,7 @@ impl ProjectItemRegistry { .build_project_item_for_path_fns .iter() .rev() - .find_map(|open_project_item| open_project_item(&project, &path, window, cx)) + .find_map(|open_project_item| open_project_item(project, path, window, cx)) else { return Task::ready(Err(anyhow!("cannot open file {:?}", path.path))); }; @@ -1348,18 +1345,18 @@ impl Workspace { .timer(Duration::from_millis(100)) .await; this.update_in(cx, |this, window, cx| { - if let Some(display) = window.display(cx) { - if let Ok(display_uuid) = display.uuid() { - let window_bounds = window.inner_window_bounds(); - if let Some(database_id) = workspace_id { - cx.background_executor() - .spawn(DB.set_window_open_status( - database_id, - SerializedWindowBounds(window_bounds), - display_uuid, - )) - .detach_and_log_err(cx); - } + if let Some(display) = window.display(cx) + && let Ok(display_uuid) = display.uuid() + { + let window_bounds = window.inner_window_bounds(); + if let Some(database_id) = workspace_id { + cx.background_executor() + .spawn(DB.set_window_open_status( + database_id, + SerializedWindowBounds(window_bounds), + display_uuid, + )) + .detach_and_log_err(cx); } } this.bounds_save_task_queued.take(); @@ -1732,13 +1729,12 @@ impl Workspace { let item_map: HashMap<EntityId, &Box<dyn ItemHandle>> = pane.items().map(|item| (item.item_id(), item)).collect(); for entry in pane.activation_history() { - if entry.timestamp > recent_timestamp { - if let Some(&item) = item_map.get(&entry.entity_id) { - if let Some(typed_item) = item.act_as::<T>(cx) { - recent_timestamp = entry.timestamp; - recent_item = Some(typed_item); - } - } + if entry.timestamp > recent_timestamp + && let Some(&item) = item_map.get(&entry.entity_id) + && let Some(typed_item) = item.act_as::<T>(cx) + { + recent_timestamp = entry.timestamp; + recent_item = Some(typed_item); } } } @@ -1777,19 +1773,19 @@ impl Workspace { } }); - if let Some(item) = pane.active_item() { - if let Some(project_path) = item.project_path(cx) { - let fs_path = self.project.read(cx).absolute_path(&project_path, cx); + if let Some(item) = pane.active_item() + && let Some(project_path) = item.project_path(cx) + { + let fs_path = self.project.read(cx).absolute_path(&project_path, cx); - if let Some(fs_path) = &fs_path { - abs_paths_opened - .entry(fs_path.clone()) - .or_default() - .insert(project_path.clone()); - } - - history.insert(project_path, (fs_path, std::usize::MAX)); + if let Some(fs_path) = &fs_path { + abs_paths_opened + .entry(fs_path.clone()) + .or_default() + .insert(project_path.clone()); } + + history.insert(project_path, (fs_path, std::usize::MAX)); } } @@ -1808,7 +1804,7 @@ impl Workspace { .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id)) }); - latest_project_path_opened.map_or(true, |path| path == history_path) + latest_project_path_opened.is_none_or(|path| path == history_path) }) } @@ -2253,29 +2249,28 @@ impl Workspace { .count() })?; - if let Some(active_call) = active_call { - if close_intent != CloseIntent::Quit - && workspace_count == 1 - && active_call.read_with(cx, |call, _| call.room().is_some())? - { - let answer = cx.update(|window, cx| { - window.prompt( - PromptLevel::Warning, - "Do you want to leave the current call?", - None, - &["Close window and hang up", "Cancel"], - cx, - ) - })?; + if let Some(active_call) = active_call + && close_intent != CloseIntent::Quit + && workspace_count == 1 + && active_call.read_with(cx, |call, _| call.room().is_some())? + { + let answer = cx.update(|window, cx| { + window.prompt( + PromptLevel::Warning, + "Do you want to leave the current call?", + None, + &["Close window and hang up", "Cancel"], + cx, + ) + })?; - if answer.await.log_err() == Some(1) { - return anyhow::Ok(false); - } else { - active_call - .update(cx, |call, cx| call.hang_up(cx))? - .await - .log_err(); - } + if answer.await.log_err() == Some(1) { + return anyhow::Ok(false); + } else { + active_call + .update(cx, |call, cx| call.hang_up(cx))? + .await + .log_err(); } } @@ -2289,7 +2284,7 @@ impl Workspace { // the current session. if close_intent != CloseIntent::Quit && !save_last_workspace - && save_result.as_ref().map_or(false, |&res| res) + && save_result.as_ref().is_ok_and(|&res| res) { this.update_in(cx, |this, window, cx| this.remove_from_session(window, cx))? .await; @@ -2429,7 +2424,7 @@ impl Workspace { ); window.prompt( PromptLevel::Warning, - &"Do you want to save all changes in the following files?", + "Do you want to save all changes in the following files?", Some(&detail), &["Save all", "Discard all", "Cancel"], cx, @@ -2451,10 +2446,10 @@ impl Workspace { for (pane, item) in dirty_items { let (singleton, project_entry_ids) = cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?; - if singleton || !project_entry_ids.is_empty() { - if !Pane::save_item(project.clone(), &pane, &*item, save_intent, cx).await? { - return Ok(false); - } + if (singleton || !project_entry_ids.is_empty()) + && !Pane::save_item(project.clone(), &pane, &*item, save_intent, cx).await? + { + return Ok(false); } } Ok(true) @@ -2508,8 +2503,6 @@ impl Workspace { window: &mut Window, cx: &mut Context<Self>, ) -> Task<Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>> { - log::info!("open paths {abs_paths:?}"); - let fs = self.app_state.fs.clone(); // Sort the paths to ensure we add worktrees for parents before their children. @@ -2655,6 +2648,7 @@ impl Workspace { files: false, directories: true, multiple: true, + prompt: None, }, DirectoryLister::Project(self.project.clone()), window, @@ -2764,9 +2758,9 @@ impl Workspace { let item = pane.read(cx).active_item(); let pane = pane.downgrade(); - window.spawn(cx, async move |mut cx| { + window.spawn(cx, async move |cx| { if let Some(item) = item { - Pane::save_item(project, &pane, item.as_ref(), save_intent, &mut cx) + Pane::save_item(project, &pane, item.as_ref(), save_intent, cx) .await .map(|_| ()) } else { @@ -3082,14 +3076,12 @@ impl Workspace { let mut focus_center = false; for dock in self.all_docks() { dock.update(cx, |dock, cx| { - if Some(dock.position()) != dock_to_reveal { - if let Some(panel) = dock.active_panel() { - if panel.is_zoomed(window, cx) { - focus_center |= - panel.panel_focus_handle(cx).contains_focused(window, cx); - dock.set_open(false, window, cx); - } - } + if Some(dock.position()) != dock_to_reveal + && let Some(panel) = dock.active_panel() + && panel.is_zoomed(window, cx) + { + focus_center |= panel.panel_focus_handle(cx).contains_focused(window, cx); + dock.set_open(false, window, cx); } }); } @@ -3289,7 +3281,8 @@ impl Workspace { let task = self.load_path(project_path.clone(), window, cx); window.spawn(cx, async move |cx| { let (project_entry_id, build_item) = task.await?; - let result = pane.update_in(cx, |pane, window, cx| { + + pane.update_in(cx, |pane, window, cx| { pane.open_item( project_entry_id, project_path, @@ -3301,8 +3294,7 @@ impl Workspace { cx, build_item, ) - }); - result + }) }) } @@ -3330,10 +3322,10 @@ impl Workspace { .downgrade() }); - if let Member::Pane(center_pane) = &self.center.root { - if center_pane.read(cx).items_len() == 0 { - return self.open_path(path, Some(pane), true, window, cx); - } + if let Member::Pane(center_pane) = &self.center.root + && center_pane.read(cx).items_len() == 0 + { + return self.open_path(path, Some(pane), true, window, cx); } let project_path = path.into(); @@ -3395,10 +3387,10 @@ impl Workspace { if let Some(entry_id) = entry_id { item = pane.read(cx).item_for_entry(entry_id, cx); } - if item.is_none() { - if let Some(project_path) = project_path { - item = pane.read(cx).item_for_path(project_path, cx); - } + if item.is_none() + && let Some(project_path) = project_path + { + item = pane.read(cx).item_for_path(project_path, cx); } item.and_then(|item| item.downcast::<T>()) @@ -3442,12 +3434,11 @@ impl Workspace { 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 { - if let Some(preview_item_id) = pane.preview_item_id() { - if preview_item_id != item_id { - destination_index = pane.close_current_preview_item(window, 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); } pane.set_preview_item_id(Some(item.item_id()), cx) }); @@ -3886,14 +3877,14 @@ impl Workspace { pane.track_alternate_file_items(); }); if *local { - self.unfollow_in_pane(&pane, window, cx); + self.unfollow_in_pane(pane, window, cx); } serialize_workspace = *focus_changed || pane != self.active_pane(); if pane == self.active_pane() { self.active_item_path_changed(window, cx); self.update_active_view_for_followers(window, cx); } else if *local { - self.set_active_pane(&pane, window, cx); + self.set_active_pane(pane, window, cx); } } pane::Event::UserSavedItem { item, save_intent } => { @@ -3914,10 +3905,10 @@ impl Workspace { pane::Event::RemovedItem { item } => { cx.emit(Event::ActiveItemChanged); self.update_window_edited(window, cx); - if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(item.item_id()) { - if entry.get().entity_id() == pane.entity_id() { - entry.remove(); - } + if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(item.item_id()) + && entry.get().entity_id() == pane.entity_id() + { + entry.remove(); } } pane::Event::Focus => { @@ -4107,14 +4098,13 @@ impl Workspace { pub fn focused_pane(&self, window: &Window, cx: &App) -> Entity<Pane> { for dock in self.all_docks() { - if dock.focus_handle(cx).contains_focused(window, cx) { - if let Some(pane) = dock + if dock.focus_handle(cx).contains_focused(window, cx) + && let Some(pane) = dock .read(cx) .active_panel() .and_then(|panel| panel.pane(cx)) - { - return pane; - } + { + return pane; } } self.active_pane().clone() @@ -4395,10 +4385,10 @@ impl Workspace { title.push_str(" ↗"); } - if let Some(last_title) = self.last_window_title.as_ref() { - if &title == last_title { - return; - } + if let Some(last_title) = self.last_window_title.as_ref() + && &title == last_title + { + return; } window.set_window_title(&title); self.last_window_title = Some(title); @@ -4577,10 +4567,8 @@ impl Workspace { } })??; - if should_add_view { - if let Some(view) = update_active_view.view { - Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await? - } + if should_add_view && let Some(view) = update_active_view.view { + Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await? } } proto::update_followers::Variant::UpdateView(update_view) => { @@ -4776,40 +4764,40 @@ impl Workspace { if window.is_window_active() { let (active_item, panel_id) = self.active_item_for_followers(window, cx); - if let Some(item) = active_item { - if item.item_focus_handle(cx).contains_focused(window, cx) { - let leader_id = self - .pane_for(&*item) - .and_then(|pane| self.leader_for_pane(&pane)); - let leader_peer_id = match leader_id { - Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id), - Some(CollaboratorId::Agent) | None => None, - }; + if let Some(item) = active_item + && item.item_focus_handle(cx).contains_focused(window, cx) + { + let leader_id = self + .pane_for(&*item) + .and_then(|pane| self.leader_for_pane(&pane)); + let leader_peer_id = match leader_id { + Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id), + Some(CollaboratorId::Agent) | None => None, + }; - if let Some(item) = item.to_followable_item_handle(cx) { - let id = item - .remote_id(&self.app_state.client, window, cx) - .map(|id| id.to_proto()); + if let Some(item) = item.to_followable_item_handle(cx) { + let id = item + .remote_id(&self.app_state.client, window, cx) + .map(|id| id.to_proto()); - if let Some(id) = id { - if let Some(variant) = item.to_state_proto(window, cx) { - let view = Some(proto::View { - id: id.clone(), - leader_id: leader_peer_id, - variant: Some(variant), - panel_id: panel_id.map(|id| id as i32), - }); + if let Some(id) = id + && let Some(variant) = item.to_state_proto(window, cx) + { + let view = Some(proto::View { + id: id.clone(), + leader_id: leader_peer_id, + variant: Some(variant), + panel_id: panel_id.map(|id| id as i32), + }); - is_project_item = item.is_project_item(window, cx); - update = proto::UpdateActiveView { - view, - // TODO: Remove after version 0.145.x stabilizes. - id, - leader_id: leader_peer_id, - }; - } + is_project_item = item.is_project_item(window, cx); + update = proto::UpdateActiveView { + view, + // TODO: Remove after version 0.145.x stabilizes. + id, + leader_id: leader_peer_id, }; - } + }; } } } @@ -4834,16 +4822,14 @@ impl Workspace { let mut active_item = None; let mut panel_id = None; for dock in self.all_docks() { - if dock.focus_handle(cx).contains_focused(window, cx) { - if let Some(panel) = dock.read(cx).active_panel() { - if let Some(pane) = panel.pane(cx) { - if let Some(item) = pane.read(cx).active_item() { - active_item = Some(item); - panel_id = panel.remote_id(); - break; - } - } - } + if dock.focus_handle(cx).contains_focused(window, cx) + && let Some(panel) = dock.read(cx).active_panel() + && let Some(pane) = panel.pane(cx) + && let Some(item) = pane.read(cx).active_item() + { + active_item = Some(item); + panel_id = panel.remote_id(); + break; } } @@ -4971,10 +4957,10 @@ impl Workspace { let state = self.follower_states.get(&peer_id.into())?; let mut item_to_activate = None; if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { - if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { - if leader_in_this_project || !item.view.is_project_item(window, cx) { - item_to_activate = Some((item.location, item.view.boxed_clone())); - } + if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) + && (leader_in_this_project || !item.view.is_project_item(window, cx)) + { + item_to_activate = Some((item.location, item.view.boxed_clone())); } } else if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &state.center_pane, window, cx) @@ -5145,13 +5131,11 @@ impl Workspace { self.panes.retain(|p| p != pane); if let Some(focus_on) = focus_on { focus_on.update(cx, |pane, cx| window.focus(&pane.focus_handle(cx))); - } else { - if self.active_pane() == pane { - self.panes - .last() - .unwrap() - .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx))); - } + } else if self.active_pane() == pane { + self.panes + .last() + .unwrap() + .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx))); } if self.last_active_center_pane == Some(pane.downgrade()) { self.last_active_center_pane = None; @@ -5905,7 +5889,6 @@ impl Workspace { pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) { if cx.stop_active_drag(window) { - return; } else if let Some((notification_id, _)) = self.notifications.pop() { dismiss_app_notification(¬ification_id, cx); } else { @@ -6081,10 +6064,10 @@ fn open_items( project_paths_to_open .iter_mut() .for_each(|(_, project_path)| { - if let Some(project_path_to_open) = project_path { - if restored_project_paths.contains(project_path_to_open) { - *project_path = None; - } + if let Some(project_path_to_open) = project_path + && restored_project_paths.contains(project_path_to_open) + { + *project_path = None; } }); } else { @@ -6111,24 +6094,24 @@ fn open_items( // We only want to open file paths here. If one of the items // here is a directory, it was already opened further above // with a `find_or_create_worktree`. - if let Ok(task) = abs_path_task { - if task.await.map_or(true, |p| p.is_file()) { - return Some(( - ix, - workspace - .update_in(cx, |workspace, window, cx| { - workspace.open_path( - file_project_path, - None, - true, - window, - cx, - ) - }) - .log_err()? - .await, - )); - } + if let Ok(task) = abs_path_task + && task.await.is_none_or(|p| p.is_file()) + { + return Some(( + ix, + workspace + .update_in(cx, |workspace, window, cx| { + workspace.open_path( + file_project_path, + None, + true, + window, + cx, + ) + }) + .log_err()? + .await, + )); } None }) @@ -6730,10 +6713,10 @@ impl WorkspaceStore { .update(cx, |workspace, window, cx| { let handler_response = workspace.handle_follow(follower.project_id, window, cx); - if let Some(active_view) = handler_response.active_view.clone() { - if workspace.project.read(cx).remote_id() == follower.project_id { - response.active_view = Some(active_view) - } + if let Some(active_view) = handler_response.active_view.clone() + && workspace.project.read(cx).remote_id() == follower.project_id + { + response.active_view = Some(active_view) } }) .is_ok() @@ -6820,14 +6803,6 @@ impl WorkspaceHandle for Entity<Workspace> { } } -impl std::fmt::Debug for OpenPaths { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("OpenPaths") - .field("paths", &self.paths) - .finish() - } -} - pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> { DB.last_workspace().await.log_err().flatten() } @@ -6975,34 +6950,35 @@ async fn join_channel_internal( } // If you are the first to join a channel, see if you should share your project. - if room.remote_participants().is_empty() && !room.local_participant_is_guest() { - if let Some(workspace) = requesting_window { - let project = workspace.update(cx, |workspace, _, cx| { - let project = workspace.project.read(cx); + if room.remote_participants().is_empty() + && !room.local_participant_is_guest() + && let Some(workspace) = requesting_window + { + let project = workspace.update(cx, |workspace, _, cx| { + let project = workspace.project.read(cx); - if !CallSettings::get_global(cx).share_on_join { - return None; - } - - if (project.is_local() || project.is_via_ssh()) - && project.visible_worktrees(cx).any(|tree| { - tree.read(cx) - .root_entry() - .map_or(false, |entry| entry.is_dir()) - }) - { - Some(workspace.project.clone()) - } else { - None - } - }); - if let Ok(Some(project)) = project { - return Some(cx.spawn(async move |room, cx| { - room.update(cx, |room, cx| room.share_project(project, cx))? - .await?; - Ok(()) - })); + if !CallSettings::get_global(cx).share_on_join { + return None; } + + if (project.is_local() || project.is_via_ssh()) + && project.visible_worktrees(cx).any(|tree| { + tree.read(cx) + .root_entry() + .is_some_and(|entry| entry.is_dir()) + }) + { + Some(workspace.project.clone()) + } else { + None + } + }); + if let Ok(Some(project)) = project { + return Some(cx.spawn(async move |room, cx| { + room.update(cx, |room, cx| room.share_project(project, cx))? + .await?; + Ok(()) + })); } } @@ -7179,9 +7155,9 @@ pub fn open_paths( .collect::<Vec<_>>(); cx.update(|cx| { - for window in local_workspace_windows(&cx) { - if let Ok(workspace) = window.read(&cx) { - let m = workspace.project.read(&cx).visibility_for_paths( + for window in local_workspace_windows(cx) { + if let Ok(workspace) = window.read(cx) { + let m = workspace.project.read(cx).visibility_for_paths( &abs_paths, &all_metadatas, open_options.open_new_workspace == None, @@ -7199,35 +7175,35 @@ pub fn open_paths( } })?; - if open_options.open_new_workspace.is_none() && existing.is_none() { - if all_metadatas.iter().all(|file| !file.is_dir) { - cx.update(|cx| { - if let Some(window) = cx - .active_window() - .and_then(|window| window.downcast::<Workspace>()) - { - if let Ok(workspace) = window.read(cx) { - let project = workspace.project().read(cx); - if project.is_local() && !project.is_via_collab() { - existing = Some(window); - open_visible = OpenVisible::None; - return; - } - } + if open_options.open_new_workspace.is_none() + && existing.is_none() + && all_metadatas.iter().all(|file| !file.is_dir) + { + cx.update(|cx| { + if let Some(window) = cx + .active_window() + .and_then(|window| window.downcast::<Workspace>()) + && let Ok(workspace) = window.read(cx) + { + let project = workspace.project().read(cx); + if project.is_local() && !project.is_via_collab() { + existing = Some(window); + open_visible = OpenVisible::None; + return; } - for window in local_workspace_windows(cx) { - if let Ok(workspace) = window.read(cx) { - let project = workspace.project().read(cx); - if project.is_via_collab() { - continue; - } - existing = Some(window); - open_visible = OpenVisible::None; - break; + } + for window in local_workspace_windows(cx) { + if let Ok(workspace) = window.read(cx) { + let project = workspace.project().read(cx); + if project.is_via_collab() { + continue; } + existing = Some(window); + open_visible = OpenVisible::None; + break; } - })?; - } + } + })?; } } @@ -7338,7 +7314,7 @@ pub fn open_ssh_project_with_new_connection( ) -> Task<Result<()>> { cx.spawn(async move |cx| { let (serialized_ssh_project, workspace_id, serialized_workspace) = - serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?; + serialize_ssh_project(connection_options.clone(), paths.clone(), cx).await?; let session = match cx .update(|cx| { @@ -7392,7 +7368,7 @@ pub fn open_ssh_project_with_existing_connection( ) -> Task<Result<()>> { cx.spawn(async move |cx| { let (serialized_ssh_project, workspace_id, serialized_workspace) = - serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?; + serialize_ssh_project(connection_options.clone(), paths.clone(), cx).await?; open_ssh_project_inner( project, @@ -7661,10 +7637,9 @@ pub fn reload(cx: &mut App) { for window in workspace_windows { if let Ok(should_close) = window.update(cx, |workspace, window, cx| { workspace.prepare_to_close(CloseIntent::Quit, window, cx) - }) { - if !should_close.await? { - return Ok(()); - } + }) && !should_close.await? + { + return Ok(()); } } cx.update(|cx| cx.restart()) @@ -7920,7 +7895,6 @@ fn join_pane_into_active( cx: &mut App, ) { if pane == active_pane { - return; } else if pane.read(cx).items_len() == 0 { pane.update(cx, |_, cx| { cx.emit(pane::Event::Remove { @@ -9169,19 +9143,18 @@ mod tests { workspace.update_in(cx, |workspace, window, cx| { workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx); }); - return item; + item } fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> { - return workspace.update_in(cx, |workspace, window, cx| { - let new_pane = workspace.split_pane( + workspace.update_in(cx, |workspace, window, cx| { + workspace.split_pane( workspace.active_pane().clone(), SplitDirection::Right, window, cx, - ); - new_pane - }); + ) + }) } #[gpui::test] @@ -9437,7 +9410,7 @@ mod tests { let workspace = workspace.clone(); move |cx: &mut VisualTestContext| { workspace.update_in(cx, |workspace, window, cx| { - if let Some(_) = workspace.active_modal::<TestModal>(cx) { + if workspace.active_modal::<TestModal>(cx).is_some() { workspace.toggle_modal(window, cx, TestModal::new); workspace.toggle_modal(window, cx, TestModal::new); } else { diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 4a8c9d4666..5635347514 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -282,19 +282,17 @@ impl Settings for WorkspaceSettings { if vscode .read_bool("accessibility.dimUnfocused.enabled") .unwrap_or_default() - { - if let Some(opacity) = vscode + && let Some(opacity) = vscode .read_value("accessibility.dimUnfocused.opacity") .and_then(|v| v.as_f64()) - { - if let Some(settings) = current.active_pane_modifiers.as_mut() { - settings.inactive_opacity = Some(opacity as f32) - } else { - current.active_pane_modifiers = Some(ActivePanelModifiers { - inactive_opacity: Some(opacity as f32), - ..Default::default() - }) - } + { + if let Some(settings) = current.active_pane_modifiers.as_mut() { + settings.inactive_opacity = Some(opacity as f32) + } else { + current.active_pane_modifiers = Some(ActivePanelModifiers { + inactive_opacity: Some(opacity as f32), + ..Default::default() + }) } } @@ -345,13 +343,11 @@ impl Settings for WorkspaceSettings { .read_value("workbench.editor.limit.value") .and_then(|v| v.as_u64()) .and_then(|n| NonZeroUsize::new(n as usize)) - { - if vscode + && vscode .read_bool("workbench.editor.limit.enabled") .unwrap_or_default() - { - current.max_tabs = Some(n) - } + { + current.max_tabs = Some(n) } // some combination of "window.restoreWindows" and "workbench.startupEditor" might diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index b5a0f71e81..d38f3cac3d 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -1522,10 +1522,10 @@ impl LocalWorktree { // reasonable limit { const FILE_SIZE_MAX: u64 = 6 * 1024 * 1024 * 1024; // 6GB - if let Ok(Some(metadata)) = fs.metadata(&abs_path).await { - if metadata.len >= FILE_SIZE_MAX { - anyhow::bail!("File is too large to load"); - } + if let Ok(Some(metadata)) = fs.metadata(&abs_path).await + && metadata.len >= FILE_SIZE_MAX + { + anyhow::bail!("File is too large to load"); } } let text = fs.load(&abs_path).await?; @@ -2503,10 +2503,10 @@ impl Snapshot { if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) { entries_by_path_edits.push(Edit::Remove(PathKey(path.clone()))); } - if let Some(old_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), &()) { - if old_entry.id != entry.id { - entries_by_id_edits.push(Edit::Remove(old_entry.id)); - } + if let Some(old_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), &()) + && old_entry.id != entry.id + { + entries_by_id_edits.push(Edit::Remove(old_entry.id)); } entries_by_id_edits.push(Edit::Insert(PathEntry { id: entry.id, @@ -2747,20 +2747,19 @@ impl LocalSnapshot { } } - if entry.kind == EntryKind::PendingDir { - if let Some(existing_entry) = + if entry.kind == EntryKind::PendingDir + && let Some(existing_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), &()) - { - entry.kind = existing_entry.kind; - } + { + entry.kind = existing_entry.kind; } let scan_id = self.scan_id; let removed = self.entries_by_path.insert_or_replace(entry.clone(), &()); - if let Some(removed) = removed { - if removed.id != entry.id { - self.entries_by_id.remove(&removed.id, &()); - } + if let Some(removed) = removed + && removed.id != entry.id + { + self.entries_by_id.remove(&removed.id, &()); } self.entries_by_id.insert_or_replace( PathEntry { @@ -3199,7 +3198,7 @@ impl BackgroundScannerState { } async fn is_git_dir(path: &Path, fs: &dyn Fs) -> bool { - if path.file_name() == Some(&*DOT_GIT) { + if path.file_name() == Some(*DOT_GIT) { return true; } @@ -3394,12 +3393,10 @@ impl File { let disk_state = if proto.is_deleted { DiskState::Deleted + } else if let Some(mtime) = proto.mtime.map(&Into::into) { + DiskState::Present { mtime } } else { - if let Some(mtime) = proto.mtime.map(&Into::into) { - DiskState::Present { mtime } - } else { - DiskState::New - } + DiskState::New }; Ok(Self { @@ -3575,7 +3572,7 @@ impl<'a> cursor_location: &Dimensions<TraversalProgress<'a>, GitSummary>, _: &(), ) -> Ordering { - self.cmp_path(&cursor_location.0.max_path) + self.cmp_path(cursor_location.0.max_path) } } @@ -4075,10 +4072,10 @@ impl BackgroundScanner { } } - let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { + let parent_dir_is_loaded = relative_path.parent().is_none_or(|parent| { snapshot .entry_for_path(parent) - .map_or(false, |entry| entry.kind == EntryKind::Dir) + .is_some_and(|entry| entry.kind == EntryKind::Dir) }); if !parent_dir_is_loaded { log::debug!("ignoring event {relative_path:?} within unloaded directory"); @@ -4138,13 +4135,13 @@ impl BackgroundScanner { let root_path = state.snapshot.abs_path.clone(); for path in paths { for ancestor in path.ancestors() { - if let Some(entry) = state.snapshot.entry_for_path(ancestor) { - if entry.kind == EntryKind::UnloadedDir { - let abs_path = root_path.as_path().join(ancestor); - state.enqueue_scan_dir(abs_path.into(), entry, &scan_job_tx); - state.paths_to_scan.insert(path.clone()); - break; - } + if let Some(entry) = state.snapshot.entry_for_path(ancestor) + && entry.kind == EntryKind::UnloadedDir + { + let abs_path = root_path.as_path().join(ancestor); + state.enqueue_scan_dir(abs_path.into(), entry, &scan_job_tx); + state.paths_to_scan.insert(path.clone()); + break; } } } @@ -4214,11 +4211,10 @@ impl BackgroundScanner { // Recursively load directories from the file system. job = scan_jobs_rx.recv().fuse() => { let Ok(job) = job else { break }; - if let Err(err) = self.scan_dir(&job).await { - if job.path.as_ref() != Path::new("") { + if let Err(err) = self.scan_dir(&job).await + && job.path.as_ref() != Path::new("") { log::error!("error scanning directory {:?}: {}", job.abs_path, err); } - } } } } @@ -4554,18 +4550,18 @@ impl BackgroundScanner { state.insert_entry(fs_entry.clone(), self.fs.as_ref(), self.watcher.as_ref()); - if path.as_ref() == Path::new("") { - if let Some((ignores, repo)) = new_ancestor_repo.take() { - log::trace!("updating ancestor git repository"); - state.snapshot.ignores_by_parent_abs_path.extend(ignores); - if let Some((ancestor_dot_git, work_directory)) = repo { - state.insert_git_repository_for_path( - work_directory, - ancestor_dot_git.as_path().into(), - self.fs.as_ref(), - self.watcher.as_ref(), - ); - } + if path.as_ref() == Path::new("") + && let Some((ignores, repo)) = new_ancestor_repo.take() + { + log::trace!("updating ancestor git repository"); + state.snapshot.ignores_by_parent_abs_path.extend(ignores); + if let Some((ancestor_dot_git, work_directory)) = repo { + state.insert_git_repository_for_path( + work_directory, + ancestor_dot_git.as_path().into(), + self.fs.as_ref(), + self.watcher.as_ref(), + ); } } } @@ -4590,13 +4586,12 @@ impl BackgroundScanner { if !path .components() .any(|component| component.as_os_str() == *DOT_GIT) + && let Some(local_repo) = snapshot.local_repo_for_work_directory_path(path) { - if let Some(local_repo) = snapshot.local_repo_for_work_directory_path(path) { - let id = local_repo.work_directory_id; - log::debug!("remove repo path: {:?}", path); - snapshot.git_repositories.remove(&id); - return Some(()); - } + let id = local_repo.work_directory_id; + log::debug!("remove repo path: {:?}", path); + snapshot.git_repositories.remove(&id); + return Some(()); } Some(()) @@ -4633,7 +4628,7 @@ impl BackgroundScanner { while let Some(parent_abs_path) = ignores_to_update.next() { while ignores_to_update .peek() - .map_or(false, |p| p.starts_with(&parent_abs_path)) + .is_some_and(|p| p.starts_with(&parent_abs_path)) { ignores_to_update.next().unwrap(); } @@ -4738,10 +4733,10 @@ impl BackgroundScanner { let state = &mut self.state.lock(); for edit in &entries_by_path_edits { - if let Edit::Insert(entry) = edit { - if let Err(ix) = state.changed_paths.binary_search(&entry.path) { - state.changed_paths.insert(ix, entry.path.clone()); - } + if let Edit::Insert(entry) = edit + && let Err(ix) = state.changed_paths.binary_search(&entry.path) + { + state.changed_paths.insert(ix, entry.path.clone()); } } @@ -4800,9 +4795,7 @@ impl BackgroundScanner { for (&work_directory_id, entry) in snapshot.git_repositories.iter() { let exists_in_snapshot = snapshot .entry_for_id(work_directory_id) - .map_or(false, |entry| { - snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some() - }); + .is_some_and(|entry| snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some()); if exists_in_snapshot || matches!( @@ -4927,10 +4920,10 @@ fn build_diff( new_paths.next(); for path in event_paths { let path = PathKey(path.clone()); - if old_paths.item().map_or(false, |e| e.path < path.0) { + if old_paths.item().is_some_and(|e| e.path < path.0) { old_paths.seek_forward(&path, Bias::Left); } - if new_paths.item().map_or(false, |e| e.path < path.0) { + if new_paths.item().is_some_and(|e| e.path < path.0) { new_paths.seek_forward(&path, Bias::Left); } loop { @@ -4980,7 +4973,7 @@ fn build_diff( let is_newly_loaded = phase == InitialScan || last_newly_loaded_dir_path .as_ref() - .map_or(false, |dir| new_entry.path.starts_with(dir)); + .is_some_and(|dir| new_entry.path.starts_with(dir)); changes.push(( new_entry.path.clone(), new_entry.id, @@ -4998,7 +4991,7 @@ fn build_diff( let is_newly_loaded = phase == InitialScan || last_newly_loaded_dir_path .as_ref() - .map_or(false, |dir| new_entry.path.starts_with(dir)); + .is_some_and(|dir| new_entry.path.starts_with(dir)); changes.push(( new_entry.path.clone(), new_entry.id, @@ -5287,13 +5280,12 @@ impl<'a> Traversal<'a> { while let Some(entry) = self.cursor.item() { self.cursor .seek_forward(&TraversalTarget::successor(&entry.path), Bias::Left); - if let Some(entry) = self.cursor.item() { - if (self.include_files || !entry.is_file()) - && (self.include_dirs || !entry.is_dir()) - && (self.include_ignored || !entry.is_ignored || entry.is_always_included) - { - return true; - } + if let Some(entry) = self.cursor.item() + && (self.include_files || !entry.is_file()) + && (self.include_dirs || !entry.is_dir()) + && (self.include_ignored || !entry.is_ignored || entry.is_always_included) + { + return true; } } false @@ -5364,13 +5356,13 @@ impl PathTarget<'_> { impl<'a, S: Summary> SeekTarget<'a, PathSummary<S>, PathProgress<'a>> for PathTarget<'_> { fn cmp(&self, cursor_location: &PathProgress<'a>, _: &S::Context) -> Ordering { - self.cmp_path(&cursor_location.max_path) + self.cmp_path(cursor_location.max_path) } } impl<'a, S: Summary> SeekTarget<'a, PathSummary<S>, TraversalProgress<'a>> for PathTarget<'_> { fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &S::Context) -> Ordering { - self.cmp_path(&cursor_location.max_path) + self.cmp_path(cursor_location.max_path) } } @@ -5396,7 +5388,7 @@ impl<'a> TraversalTarget<'a> { fn cmp_progress(&self, progress: &TraversalProgress) -> Ordering { match self { - TraversalTarget::Path(path) => path.cmp_path(&progress.max_path), + TraversalTarget::Path(path) => path.cmp_path(progress.max_path), TraversalTarget::Count { count, include_files, @@ -5437,11 +5429,11 @@ impl<'a> Iterator for ChildEntriesIter<'a> { type Item = &'a Entry; fn next(&mut self) -> Option<Self::Item> { - if let Some(item) = self.traversal.entry() { - if item.path.starts_with(self.parent_path) { - self.traversal.advance_to_sibling(); - return Some(item); - } + if let Some(item) = self.traversal.entry() + && item.path.starts_with(self.parent_path) + { + self.traversal.advance_to_sibling(); + return Some(item); } None } @@ -5551,7 +5543,7 @@ fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<Path>, let mut repository_dir_abs_path = dot_git_abs_path.clone(); let mut common_dir_abs_path = dot_git_abs_path.clone(); - if let Some(path) = smol::block_on(fs.load(&dot_git_abs_path)) + if let Some(path) = smol::block_on(fs.load(dot_git_abs_path)) .ok() .as_ref() .and_then(|contents| parse_gitfile(contents).log_err()) @@ -5564,12 +5556,10 @@ fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<Path>, repository_dir_abs_path = Path::new(&path).into(); common_dir_abs_path = repository_dir_abs_path.clone(); if let Some(commondir_contents) = smol::block_on(fs.load(&path.join("commondir"))).ok() - { - if let Some(commondir_path) = + && let Some(commondir_path) = smol::block_on(fs.canonicalize(&path.join(commondir_contents.trim()))).log_err() - { - common_dir_abs_path = commondir_path.as_path().into(); - } + { + common_dir_abs_path = commondir_path.as_path().into(); } } }; diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index 26cf16e8f6..b18d3509be 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -82,7 +82,7 @@ impl Settings for WorktreeSettings { .ancestors() .map(|a| a.to_string_lossy().into()) }) - .filter(|p| p != "") + .filter(|p: &String| !p.is_empty()) .collect(); file_scan_exclusions.sort(); private_files.sort(); diff --git a/crates/zed/build.rs b/crates/zed/build.rs index eb18617add..c6d943a459 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -23,22 +23,20 @@ fn main() { "cargo:rustc-env=TARGET={}", std::env::var("TARGET").unwrap() ); - if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() { - if output.status.success() { - let git_sha = String::from_utf8_lossy(&output.stdout); - let git_sha = git_sha.trim(); + if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() + && output.status.success() + { + let git_sha = String::from_utf8_lossy(&output.stdout); + let git_sha = git_sha.trim(); - println!("cargo:rustc-env=ZED_COMMIT_SHA={git_sha}"); + println!("cargo:rustc-env=ZED_COMMIT_SHA={git_sha}"); - if let Ok(build_profile) = std::env::var("PROFILE") { - if build_profile == "release" { - // This is currently the best way to make `cargo build ...`'s build script - // to print something to stdout without extra verbosity. - println!( - "cargo:warning=Info: using '{git_sha}' hash for ZED_COMMIT_SHA env var" - ); - } - } + if let Ok(build_profile) = std::env::var("PROFILE") + && build_profile == "release" + { + // This is currently the best way to make `cargo build ...`'s build script + // to print something to stdout without extra verbosity. + println!("cargo:warning=Info: using '{git_sha}' hash for ZED_COMMIT_SHA env var"); } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 2a82f81b5b..851c4e79f1 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -80,12 +80,9 @@ fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) { #[cfg(unix)] { - match kind { - io::ErrorKind::PermissionDenied => { - error_kind_details.push_str("\n\nConsider using chown and chmod tools for altering the directories permissions if your user has corresponding rights.\ - \nFor example, `sudo chown $(whoami):staff ~/.config` and `chmod +uwrx ~/.config`"); - } - _ => {} + if kind == io::ErrorKind::PermissionDenied { + error_kind_details.push_str("\n\nConsider using chown and chmod tools for altering the directories permissions if your user has corresponding rights.\ + \nFor example, `sudo chown $(whoami):staff ~/.config` and `chmod +uwrx ~/.config`"); } } @@ -371,9 +368,9 @@ pub fn main() { { cx.spawn({ let app_state = app_state.clone(); - async move |mut cx| { - if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await { - fail_to_open_window_async(e, &mut cx) + async move |cx| { + if let Err(e) = restore_or_create_workspace(app_state, cx).await { + fail_to_open_window_async(e, cx) } } }) @@ -690,7 +687,7 @@ pub fn main() { cx.spawn({ let client = app_state.client.clone(); - async move |cx| authenticate(client, &cx).await + async move |cx| authenticate(client, cx).await }) .detach_and_log_err(cx); @@ -722,9 +719,9 @@ pub fn main() { None => { cx.spawn({ let app_state = app_state.clone(); - async move |mut cx| { - if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await { - fail_to_open_window_async(e, &mut cx) + async move |cx| { + if let Err(e) = restore_or_create_workspace(app_state, cx).await { + fail_to_open_window_async(e, cx) } } }) @@ -795,14 +792,14 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut } if let Some(connection_options) = request.ssh_connection { - cx.spawn(async move |mut cx| { + cx.spawn(async move |cx| { let paths: Vec<PathBuf> = request.open_paths.into_iter().map(PathBuf::from).collect(); open_ssh_project( connection_options, paths, app_state, workspace::OpenOptions::default(), - &mut cx, + cx, ) .await }) @@ -813,7 +810,7 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut let mut task = None; if !request.open_paths.is_empty() || !request.diff_paths.is_empty() { let app_state = app_state.clone(); - task = Some(cx.spawn(async move |mut cx| { + task = Some(cx.spawn(async move |cx| { let paths_with_position = derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await; let (_window, results) = open_paths_with_positions( @@ -821,7 +818,7 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut &request.diff_paths, app_state, workspace::OpenOptions::default(), - &mut cx, + cx, ) .await?; for result in results.into_iter().flatten() { @@ -834,7 +831,7 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut } if !request.open_channel_notes.is_empty() || request.join_channel.is_some() { - cx.spawn(async move |mut cx| { + cx.spawn(async move |cx| { let result = maybe!(async { if let Some(task) = task { task.await?; @@ -842,7 +839,7 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut let client = app_state.client.clone(); // we continue even if authentication fails as join_channel/ open channel notes will // show a visible error message. - authenticate(client, &cx).await.log_err(); + authenticate(client, cx).await.log_err(); if let Some(channel_id) = request.join_channel { cx.update(|cx| { @@ -878,14 +875,14 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut }) .await; if let Err(err) = result { - fail_to_open_window_async(err, &mut cx); + fail_to_open_window_async(err, cx); } }) .detach() } else if let Some(task) = task { - cx.spawn(async move |mut cx| { + cx.spawn(async move |cx| { if let Err(err) = task.await { - fail_to_open_window_async(err, &mut cx); + fail_to_open_window_async(err, cx); } }) .detach(); @@ -1026,18 +1023,18 @@ async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp // Try to find an active workspace to show the toast let toast_shown = cx .update(|cx| { - if let Some(window) = cx.active_window() { - if let Some(workspace) = window.downcast::<Workspace>() { - workspace - .update(cx, |workspace, _, cx| { - workspace.show_toast( - Toast::new(NotificationId::unique::<()>(), message), - cx, - ) - }) - .ok(); - return true; - } + if let Some(window) = cx.active_window() + && let Some(workspace) = window.downcast::<Workspace>() + { + workspace + .update(cx, |workspace, _, cx| { + workspace.show_toast( + Toast::new(NotificationId::unique::<()>(), message), + cx, + ) + }) + .ok(); + return true; } false }) @@ -1117,10 +1114,8 @@ pub(crate) async fn restorable_workspace_locations( // Since last_session_window_order returns the windows ordered front-to-back // we need to open the window that was frontmost last. - if ordered { - if let Some(locations) = locations.as_mut() { - locations.reverse(); - } + if ordered && let Some(locations) = locations.as_mut() { + locations.reverse(); } locations @@ -1290,21 +1285,21 @@ fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) { if let Some(theme_selection) = theme_settings.theme_selection.as_ref() { let theme_name = theme_selection.theme(appearance); - if matches!(theme_registry.get(theme_name), Err(ThemeNotFoundError(_))) { - if let Some(theme_path) = extension_store.read(cx).path_to_extension_theme(theme_name) { - cx.spawn({ - let theme_registry = theme_registry.clone(); - let fs = fs.clone(); - async move |cx| { - theme_registry.load_user_theme(&theme_path, fs).await?; + if matches!(theme_registry.get(theme_name), Err(ThemeNotFoundError(_))) + && let Some(theme_path) = extension_store.read(cx).path_to_extension_theme(theme_name) + { + cx.spawn({ + let theme_registry = theme_registry.clone(); + let fs = fs.clone(); + async move |cx| { + theme_registry.load_user_theme(&theme_path, fs).await?; - cx.update(|cx| { - ThemeSettings::reload_current_theme(cx); - }) - } - }) - .detach_and_log_err(cx); - } + cx.update(|cx| { + ThemeSettings::reload_current_theme(cx); + }) + } + }) + .detach_and_log_err(cx); } } @@ -1313,26 +1308,24 @@ fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) { if matches!( theme_registry.get_icon_theme(icon_theme_name), Err(IconThemeNotFoundError(_)) - ) { - if let Some((icon_theme_path, icons_root_path)) = extension_store - .read(cx) - .path_to_extension_icon_theme(icon_theme_name) - { - cx.spawn({ - let theme_registry = theme_registry.clone(); - let fs = fs.clone(); - async move |cx| { - theme_registry - .load_icon_theme(&icon_theme_path, &icons_root_path, fs) - .await?; + ) && let Some((icon_theme_path, icons_root_path)) = extension_store + .read(cx) + .path_to_extension_icon_theme(icon_theme_name) + { + cx.spawn({ + let theme_registry = theme_registry.clone(); + let fs = fs.clone(); + async move |cx| { + theme_registry + .load_icon_theme(&icon_theme_path, &icons_root_path, fs) + .await?; - cx.update(|cx| { - ThemeSettings::reload_current_icon_theme(cx); - }) - } - }) - .detach_and_log_err(cx); - } + cx.update(|cx| { + ThemeSettings::reload_current_icon_theme(cx); + }) + } + }) + .detach_and_log_err(cx); } } } @@ -1381,18 +1374,15 @@ fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) { while let Some(paths) = events.next().await { for event in paths { - if fs.metadata(&event.path).await.ok().flatten().is_some() { - if let Some(theme_registry) = + if fs.metadata(&event.path).await.ok().flatten().is_some() + && let Some(theme_registry) = cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err() - { - if let Some(()) = theme_registry - .load_user_theme(&event.path, fs.clone()) - .await - .log_err() - { - cx.update(ThemeSettings::reload_current_theme).log_err(); - } - } + && let Some(()) = theme_registry + .load_user_theme(&event.path, fs.clone()) + .await + .log_err() + { + cx.update(ThemeSettings::reload_current_theme).log_err(); } } } diff --git a/crates/zed/src/reliability.rs b/crates/zed/src/reliability.rs index 0a54572f6b..f55468280c 100644 --- a/crates/zed/src/reliability.rs +++ b/crates/zed/src/reliability.rs @@ -146,19 +146,17 @@ pub fn init_panic_hook( } zlog::flush(); - if !is_pty { - if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() { - let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string(); - let panic_file_path = paths::logs_dir().join(format!("zed-{timestamp}.panic")); - let panic_file = fs::OpenOptions::new() - .write(true) - .create_new(true) - .open(&panic_file_path) - .log_err(); - if let Some(mut panic_file) = panic_file { - writeln!(&mut panic_file, "{panic_data_json}").log_err(); - panic_file.flush().log_err(); - } + if !is_pty && let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() { + let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string(); + let panic_file_path = paths::logs_dir().join(format!("zed-{timestamp}.panic")); + let panic_file = fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(&panic_file_path) + .log_err(); + if let Some(mut panic_file) = panic_file { + writeln!(&mut panic_file, "{panic_data_json}").log_err(); + panic_file.flush().log_err(); } } @@ -459,10 +457,10 @@ pub fn monitor_main_thread_hangs( continue; }; - if let Some(response) = http_client.send(request).await.log_err() { - if response.status() != 200 { - log::error!("Failed to send hang report: HTTP {:?}", response.status()); - } + if let Some(response) = http_client.send(request).await.log_err() + && response.status() != 200 + { + log::error!("Failed to send hang report: HTTP {:?}", response.status()); } } } @@ -536,7 +534,7 @@ async fn upload_previous_panics( }); if let Some(panic) = panic - && upload_panic(&http, &panic_report_url, panic, &mut most_recent_panic).await? + && upload_panic(&http, panic_report_url, panic, &mut most_recent_panic).await? { // We've done what we can, delete the file fs::remove_file(child_path) @@ -563,10 +561,10 @@ pub async fn upload_previous_minidumps(http: Arc<HttpClientWithUrl>) -> anyhow:: } let mut json_path = child_path.clone(); json_path.set_extension("json"); - if let Ok(metadata) = serde_json::from_slice(&smol::fs::read(&json_path).await?) { - if upload_minidump( + if let Ok(metadata) = serde_json::from_slice(&smol::fs::read(&json_path).await?) + && upload_minidump( http.clone(), - &minidump_endpoint, + minidump_endpoint, smol::fs::read(&child_path) .await .context("Failed to read minidump")?, @@ -575,10 +573,9 @@ pub async fn upload_previous_minidumps(http: Arc<HttpClientWithUrl>) -> anyhow:: .await .log_err() .is_some() - { - fs::remove_file(child_path).ok(); - fs::remove_file(json_path).ok(); - } + { + fs::remove_file(child_path).ok(); + fs::remove_file(json_path).ok(); } } Ok(()) @@ -610,6 +607,9 @@ async fn upload_minidump( // TODO: add gpu-context, feature-flag-context, and more of device-context like gpu // name, screen resolution, available ram, device model, etc } + if let Some(minidump_error) = metadata.minidump_error.clone() { + form = form.text("minidump_error", minidump_error); + } let mut response_text = String::new(); let mut response = http.send_multipart_form(endpoint, form).await?; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index cfafbb70f0..232dfc42a3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -327,7 +327,7 @@ pub fn initialize_workspace( cx.subscribe_in(&workspace_handle, window, { move |workspace, _, event, window, cx| match event { workspace::Event::PaneAdded(pane) => { - initialize_pane(workspace, &pane, window, cx); + initialize_pane(workspace, pane, window, cx); } workspace::Event::OpenBundledFile { text, @@ -645,6 +645,7 @@ fn register_actions( files: true, directories: true, multiple: true, + prompt: None, }, DirectoryLister::Local( workspace.project().clone(), @@ -685,6 +686,7 @@ fn register_actions( files: true, directories: true, multiple: true, + prompt: None, }, DirectoryLister::Project(workspace.project().clone()), window, @@ -794,7 +796,7 @@ fn register_actions( .register_action(install_cli) .register_action(|_, _: &install_cli::RegisterZedScheme, window, cx| { cx.spawn_in(window, async move |workspace, cx| { - install_cli::register_zed_scheme(&cx).await?; + install_cli::register_zed_scheme(cx).await?; workspace.update_in(cx, |workspace, _, cx| { struct RegisterZedScheme; @@ -1052,27 +1054,25 @@ fn quit(_: &Quit, cx: &mut App) { }) .log_err(); - if should_confirm { - if let Some(workspace) = workspace_windows.first() { - let answer = workspace - .update(cx, |_, window, cx| { - window.prompt( - PromptLevel::Info, - "Are you sure you want to quit?", - None, - &["Quit", "Cancel"], - cx, - ) - }) - .log_err(); + if should_confirm && let Some(workspace) = workspace_windows.first() { + let answer = workspace + .update(cx, |_, window, cx| { + window.prompt( + PromptLevel::Info, + "Are you sure you want to quit?", + None, + &["Quit", "Cancel"], + cx, + ) + }) + .log_err(); - if let Some(answer) = answer { - WAITING_QUIT_CONFIRMATION.store(true, atomic::Ordering::Release); - let answer = answer.await.ok(); - WAITING_QUIT_CONFIRMATION.store(false, atomic::Ordering::Release); - if answer != Some(0) { - return Ok(()); - } + if let Some(answer) = answer { + WAITING_QUIT_CONFIRMATION.store(true, atomic::Ordering::Release); + let answer = answer.await.ok(); + WAITING_QUIT_CONFIRMATION.store(false, atomic::Ordering::Release); + if answer != Some(0) { + return Ok(()); } } } @@ -1084,10 +1084,9 @@ fn quit(_: &Quit, cx: &mut App) { workspace.prepare_to_close(CloseIntent::Quit, window, cx) }) .log_err() + && !should_close.await? { - if !should_close.await? { - return Ok(()); - } + return Ok(()); } } cx.update(|cx| cx.quit())?; @@ -1621,25 +1620,24 @@ fn open_local_file( .read_with(cx, |tree, _| tree.abs_path().join(settings_relative_path))?; let fs = project.read_with(cx, |project, _| project.fs().clone())?; - let file_exists = fs - .metadata(&full_path) + + fs.metadata(&full_path) .await .ok() .flatten() - .map_or(false, |metadata| !metadata.is_dir && !metadata.is_fifo); - file_exists + .is_some_and(|metadata| !metadata.is_dir && !metadata.is_fifo) }; if !file_exists { - if let Some(dir_path) = settings_relative_path.parent() { - if worktree.read_with(cx, |tree, _| tree.entry_for_path(dir_path).is_none())? { - project - .update(cx, |project, cx| { - project.create_entry((tree_id, dir_path), true, cx) - })? - .await - .context("worktree was removed")?; - } + if let Some(dir_path) = settings_relative_path.parent() + && worktree.read_with(cx, |tree, _| tree.entry_for_path(dir_path).is_none())? + { + project + .update(cx, |project, cx| { + project.create_entry((tree_id, dir_path), true, cx) + })? + .await + .context("worktree was removed")?; } if worktree.read_with(cx, |tree, _| { @@ -1665,12 +1663,12 @@ fn open_local_file( editor .downgrade() .update(cx, |editor, cx| { - if let Some(buffer) = editor.buffer().read(cx).as_singleton() { - if buffer.read(cx).is_empty() { - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, initial_contents)], None, cx) - }); - } + if let Some(buffer) = editor.buffer().read(cx).as_singleton() + && buffer.read(cx).is_empty() + { + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, initial_contents)], None, cx) + }); } }) .ok(); diff --git a/crates/zed/src/zed/component_preview.rs b/crates/zed/src/zed/component_preview.rs index 4609ecce9b..d855fc3af7 100644 --- a/crates/zed/src/zed/component_preview.rs +++ b/crates/zed/src/zed/component_preview.rs @@ -318,25 +318,25 @@ impl ComponentPreview { let lowercase_scope = scope_name.to_lowercase(); let lowercase_desc = description.to_lowercase(); - if lowercase_scopeless.contains(&lowercase_filter) { - if let Some(index) = lowercase_scopeless.find(&lowercase_filter) { - let end = index + lowercase_filter.len(); + if lowercase_scopeless.contains(&lowercase_filter) + && let Some(index) = lowercase_scopeless.find(&lowercase_filter) + { + let end = index + lowercase_filter.len(); - if end <= scopeless_name.len() { - let mut positions = Vec::new(); - for i in index..end { - if scopeless_name.is_char_boundary(i) { - positions.push(i); - } + if end <= scopeless_name.len() { + let mut positions = Vec::new(); + for i in index..end { + if scopeless_name.is_char_boundary(i) { + positions.push(i); } + } - if !positions.is_empty() { - scope_groups - .entry(component.scope()) - .or_insert_with(Vec::new) - .push((component.clone(), Some(positions))); - continue; - } + if !positions.is_empty() { + scope_groups + .entry(component.scope()) + .or_insert_with(Vec::new) + .push((component.clone(), Some(positions))); + continue; } } } @@ -372,32 +372,32 @@ impl ComponentPreview { scopes.sort_by_key(|s| s.to_string()); for scope in scopes { - if let Some(components) = scope_groups.remove(&scope) { - if !components.is_empty() { - entries.push(PreviewEntry::Separator); - entries.push(PreviewEntry::SectionHeader(scope.to_string().into())); + if let Some(components) = scope_groups.remove(&scope) + && !components.is_empty() + { + entries.push(PreviewEntry::Separator); + entries.push(PreviewEntry::SectionHeader(scope.to_string().into())); - let mut sorted_components = components; - sorted_components.sort_by_key(|(component, _)| component.sort_name()); + let mut sorted_components = components; + sorted_components.sort_by_key(|(component, _)| component.sort_name()); - for (component, positions) in sorted_components { - entries.push(PreviewEntry::Component(component, positions)); - } + for (component, positions) in sorted_components { + entries.push(PreviewEntry::Component(component, positions)); } } } // Add uncategorized components last - if let Some(components) = scope_groups.get(&ComponentScope::None) { - if !components.is_empty() { - entries.push(PreviewEntry::Separator); - entries.push(PreviewEntry::SectionHeader("Uncategorized".into())); - let mut sorted_components = components.clone(); - sorted_components.sort_by_key(|(c, _)| c.sort_name()); + if let Some(components) = scope_groups.get(&ComponentScope::None) + && !components.is_empty() + { + entries.push(PreviewEntry::Separator); + entries.push(PreviewEntry::SectionHeader("Uncategorized".into())); + let mut sorted_components = components.clone(); + sorted_components.sort_by_key(|(c, _)| c.sort_name()); - for (component, positions) in sorted_components { - entries.push(PreviewEntry::Component(component, positions)); - } + for (component, positions) in sorted_components { + entries.push(PreviewEntry::Component(component, positions)); } } @@ -415,19 +415,20 @@ impl ComponentPreview { let filtered_components = self.filtered_components(); - if !self.filter_text.is_empty() && !matches!(self.active_page, PreviewPage::AllComponents) { - if let PreviewPage::Component(ref component_id) = self.active_page { - let component_still_visible = filtered_components - .iter() - .any(|component| component.id() == *component_id); + if !self.filter_text.is_empty() + && !matches!(self.active_page, PreviewPage::AllComponents) + && let PreviewPage::Component(ref component_id) = self.active_page + { + let component_still_visible = filtered_components + .iter() + .any(|component| component.id() == *component_id); - if !component_still_visible { - if !filtered_components.is_empty() { - let first_component = &filtered_components[0]; - self.set_active_page(PreviewPage::Component(first_component.id()), cx); - } else { - self.set_active_page(PreviewPage::AllComponents, cx); - } + if !component_still_visible { + if !filtered_components.is_empty() { + let first_component = &filtered_components[0]; + self.set_active_page(PreviewPage::Component(first_component.id()), cx); + } else { + self.set_active_page(PreviewPage::AllComponents, cx); } } } @@ -650,7 +651,7 @@ impl ComponentPreview { _window: &mut Window, _cx: &mut Context<Self>, ) -> impl IntoElement { - let component = self.component_map.get(&component_id); + let component = self.component_map.get(component_id); if let Some(component) = component { v_flex() diff --git a/crates/zed/src/zed/edit_prediction_registry.rs b/crates/zed/src/zed/edit_prediction_registry.rs index 5b0826413b..1123e53ddd 100644 --- a/crates/zed/src/zed/edit_prediction_registry.rs +++ b/crates/zed/src/zed/edit_prediction_registry.rs @@ -60,8 +60,8 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) { cx.subscribe(&user_store, { let editors = editors.clone(); let client = client.clone(); - move |user_store, event, cx| match event { - client::user::Event::PrivateUserInfoUpdated => { + move |user_store, event, cx| { + if let client::user::Event::PrivateUserInfoUpdated = event { assign_edit_prediction_providers( &editors, provider, @@ -70,7 +70,6 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) { cx, ); } - _ => {} } }) .detach(); @@ -147,7 +146,7 @@ fn assign_edit_prediction_providers( assign_edit_prediction_provider( editor, provider, - &client, + client, user_store.clone(), window, cx, @@ -204,12 +203,12 @@ fn assign_edit_prediction_provider( } EditPredictionProvider::Copilot => { if let Some(copilot) = Copilot::global(cx) { - if let Some(buffer) = singleton_buffer { - if buffer.read(cx).file().is_some() { - copilot.update(cx, |copilot, cx| { - copilot.register_buffer(&buffer, cx); - }); - } + if let Some(buffer) = singleton_buffer + && buffer.read(cx).file().is_some() + { + copilot.update(cx, |copilot, cx| { + copilot.register_buffer(&buffer, cx); + }); } let provider = cx.new(|_| CopilotCompletionProvider::new(copilot)); editor.set_edit_prediction_provider(Some(provider), window, cx); @@ -225,15 +224,15 @@ fn assign_edit_prediction_provider( if user_store.read(cx).current_user().is_some() { let mut worktree = None; - if let Some(buffer) = &singleton_buffer { - if let Some(file) = buffer.read(cx).file() { - let id = file.worktree_id(cx); - if let Some(inner_worktree) = editor - .project() - .and_then(|project| project.read(cx).worktree_for_id(id, cx)) - { - worktree = Some(inner_worktree); - } + if let Some(buffer) = &singleton_buffer + && let Some(file) = buffer.read(cx).file() + { + let id = file.worktree_id(cx); + if let Some(inner_worktree) = editor + .project() + .and_then(|project| project.read(cx).worktree_for_id(id, cx)) + { + worktree = Some(inner_worktree); } } @@ -245,12 +244,12 @@ fn assign_edit_prediction_provider( let zeta = zeta::Zeta::register(workspace, worktree, client.clone(), user_store, cx); - if let Some(buffer) = &singleton_buffer { - if buffer.read(cx).file().is_some() { - zeta.update(cx, |zeta, cx| { - zeta.register_buffer(&buffer, cx); - }); - } + if let Some(buffer) = &singleton_buffer + && buffer.read(cx).file().is_some() + { + zeta.update(cx, |zeta, cx| { + zeta.register_buffer(buffer, cx); + }); } let data_collection = diff --git a/crates/zed/src/zed/mac_only_instance.rs b/crates/zed/src/zed/mac_only_instance.rs index 716c2224e3..cb9641e9df 100644 --- a/crates/zed/src/zed/mac_only_instance.rs +++ b/crates/zed/src/zed/mac_only_instance.rs @@ -37,20 +37,19 @@ fn address() -> SocketAddr { let mut user_port = port; let mut sys = System::new_all(); sys.refresh_all(); - if let Ok(current_pid) = sysinfo::get_current_pid() { - if let Some(uid) = sys + if let Ok(current_pid) = sysinfo::get_current_pid() + && let Some(uid) = sys .process(current_pid) .and_then(|process| process.user_id()) - { - let uid_u32 = get_uid_as_u32(uid); - // Ensure that the user ID is not too large to avoid overflow when - // calculating the port number. This seems unlikely but it doesn't - // hurt to be safe. - let max_port = 65535; - let max_uid: u32 = max_port - port as u32; - let wrapped_uid: u16 = (uid_u32 % max_uid) as u16; - user_port += wrapped_uid; - } + { + let uid_u32 = get_uid_as_u32(uid); + // Ensure that the user ID is not too large to avoid overflow when + // calculating the port number. This seems unlikely but it doesn't + // hurt to be safe. + let max_port = 65535; + let max_uid: u32 = max_port - port as u32; + let wrapped_uid: u16 = (uid_u32 % max_uid) as u16; + user_port += wrapped_uid; } SocketAddr::V4(SocketAddrV4::new(LOCALHOST, user_port)) diff --git a/crates/zed/src/zed/migrate.rs b/crates/zed/src/zed/migrate.rs index 48bffb4114..2452f17d04 100644 --- a/crates/zed/src/zed/migrate.rs +++ b/crates/zed/src/zed/migrate.rs @@ -177,7 +177,7 @@ impl ToolbarItemView for MigrationBanner { })); } - return ToolbarItemLocation::Hidden; + ToolbarItemLocation::Hidden } } diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index 82d3795e94..5baf76b64c 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -123,26 +123,24 @@ impl OpenRequest { fn parse_request_path(&mut self, request_path: &str) -> Result<()> { let mut parts = request_path.split('/'); - if parts.next() == Some("channel") { - if let Some(slug) = parts.next() { - if let Some(id_str) = slug.split('-').next_back() { - if let Ok(channel_id) = id_str.parse::<u64>() { - let Some(next) = parts.next() else { - self.join_channel = Some(channel_id); - return Ok(()); - }; + if parts.next() == Some("channel") + && let Some(slug) = parts.next() + && let Some(id_str) = slug.split('-').next_back() + && let Ok(channel_id) = id_str.parse::<u64>() + { + let Some(next) = parts.next() else { + self.join_channel = Some(channel_id); + return Ok(()); + }; - if let Some(heading) = next.strip_prefix("notes#") { - self.open_channel_notes - .push((channel_id, Some(heading.to_string()))); - return Ok(()); - } - if next == "notes" { - self.open_channel_notes.push((channel_id, None)); - return Ok(()); - } - } - } + if let Some(heading) = next.strip_prefix("notes#") { + self.open_channel_notes + .push((channel_id, Some(heading.to_string()))); + return Ok(()); + } + if next == "notes" { + self.open_channel_notes.push((channel_id, None)); + return Ok(()); } } anyhow::bail!("invalid zed url: {request_path}") @@ -181,10 +179,10 @@ pub fn listen_for_cli_connections(opener: OpenListener) -> Result<()> { let sock_path = paths::data_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL_NAME)); // remove the socket if the process listening on it has died - if let Err(e) = UnixDatagram::unbound()?.connect(&sock_path) { - if e.kind() == std::io::ErrorKind::ConnectionRefused { - std::fs::remove_file(&sock_path)?; - } + if let Err(e) = UnixDatagram::unbound()?.connect(&sock_path) + && e.kind() == std::io::ErrorKind::ConnectionRefused + { + std::fs::remove_file(&sock_path)?; } let listener = UnixDatagram::bind(&sock_path)?; thread::spawn(move || { @@ -244,12 +242,12 @@ pub async fn open_paths_with_positions( .iter() .map(|path_with_position| { let path = path_with_position.path.clone(); - if let Some(row) = path_with_position.row { - if path.is_file() { - let row = row.saturating_sub(1); - let col = path_with_position.column.unwrap_or(0).saturating_sub(1); - caret_positions.insert(path.clone(), Point::new(row, col)); - } + if let Some(row) = path_with_position.row + && path.is_file() + { + let row = row.saturating_sub(1); + let col = path_with_position.column.unwrap_or(0).saturating_sub(1); + caret_positions.insert(path.clone(), Point::new(row, col)); } path }) @@ -264,10 +262,9 @@ pub async fn open_paths_with_positions( let new_path = Path::new(&diff_pair[1]).canonicalize()?; if let Ok(diff_view) = workspace.update(cx, |workspace, window, cx| { FileDiffView::open(old_path, new_path, workspace, window, cx) - }) { - if let Some(diff_view) = diff_view.await.log_err() { - items.push(Some(Ok(Box::new(diff_view)))) - } + }) && let Some(diff_view) = diff_view.await.log_err() + { + items.push(Some(Ok(Box::new(diff_view)))) } } @@ -432,13 +429,13 @@ async fn open_workspaces( .connection_options_for(ssh.host, ssh.port, ssh.user) }); if let Ok(connection_options) = connection_options { - cx.spawn(async move |mut cx| { + cx.spawn(async move |cx| { open_ssh_project( connection_options, ssh.paths.into_iter().map(PathBuf::from).collect(), app_state, OpenOptions::default(), - &mut cx, + cx, ) .await .log_err(); diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index 2b7c38f997..10d60fcd9d 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -175,14 +175,14 @@ impl Render for QuickActionBar { let code_action_menu = menu_ref .as_ref() .filter(|menu| matches!(menu, CodeContextMenu::CodeActions(..))); - code_action_menu.as_ref().map_or(false, |menu| { - matches!(menu.origin(), ContextMenuOrigin::QuickActionBar) - }) + code_action_menu + .as_ref() + .is_some_and(|menu| matches!(menu.origin(), ContextMenuOrigin::QuickActionBar)) }; let code_action_element = if is_deployed { editor.update(cx, |editor, cx| { if let Some(style) = editor.style() { - editor.render_context_menu(&style, MAX_CODE_ACTION_MENU_LINES, window, cx) + editor.render_context_menu(style, MAX_CODE_ACTION_MENU_LINES, window, cx) } else { None } diff --git a/crates/zeta/src/init.rs b/crates/zeta/src/init.rs index a01e3a89a2..6e5b31f99a 100644 --- a/crates/zeta/src/init.rs +++ b/crates/zeta/src/init.rs @@ -85,12 +85,10 @@ fn feature_gate_predict_edits_actions(cx: &mut App) { CommandPaletteFilter::update_global(cx, |filter, _cx| { if is_ai_disabled { filter.hide_action_types(&zeta_all_action_types); + } else if has_feature_flag { + filter.show_action_types(rate_completion_action_types.iter()); } else { - if has_feature_flag { - filter.show_action_types(rate_completion_action_types.iter()); - } else { - filter.hide_action_types(&rate_completion_action_types); - } + filter.hide_action_types(&rate_completion_action_types); } }); }) diff --git a/crates/zeta/src/license_detection.rs b/crates/zeta/src/license_detection.rs index fa1eabf524..022b2d19de 100644 --- a/crates/zeta/src/license_detection.rs +++ b/crates/zeta/src/license_detection.rs @@ -143,10 +143,10 @@ impl LicenseDetectionWatcher { } async fn is_path_eligible(fs: &Arc<dyn Fs>, abs_path: PathBuf) -> Option<bool> { - log::info!("checking if `{abs_path:?}` is an open source license"); + log::debug!("checking if `{abs_path:?}` is an open source license"); // Resolve symlinks so that the file size from metadata is correct. let Some(abs_path) = fs.canonicalize(&abs_path).await.ok() else { - log::info!( + log::debug!( "`{abs_path:?}` license file probably deleted (error canonicalizing the path)" ); return None; @@ -159,11 +159,11 @@ impl LicenseDetectionWatcher { let text = fs.load(&abs_path).await.log_err()?; let is_eligible = is_license_eligible_for_data_collection(&text); if is_eligible { - log::info!( + log::debug!( "`{abs_path:?}` matches a license that is eligible for data collection (if enabled)" ); } else { - log::info!( + log::debug!( "`{abs_path:?}` does not match a license that is eligible for data collection" ); } @@ -198,7 +198,7 @@ mod tests { #[test] fn test_mit_positive_detection() { - assert!(is_license_eligible_for_data_collection(&MIT_LICENSE)); + assert!(is_license_eligible_for_data_collection(MIT_LICENSE)); } #[test] diff --git a/crates/zeta/src/onboarding_modal.rs b/crates/zeta/src/onboarding_modal.rs index c2886f2864..3a58c8c7b8 100644 --- a/crates/zeta/src/onboarding_modal.rs +++ b/crates/zeta/src/onboarding_modal.rs @@ -46,7 +46,7 @@ impl ZedPredictModal { user_store.clone(), client.clone(), copilot::Copilot::global(cx) - .map_or(false, |copilot| copilot.read(cx).status().is_configured()), + .is_some_and(|copilot| copilot.read(cx).status().is_configured()), Arc::new({ let this = weak_entity.clone(); move |_window, cx| { diff --git a/crates/zeta/src/rate_completion_modal.rs b/crates/zeta/src/rate_completion_modal.rs index ac7fcade91..0cd814388a 100644 --- a/crates/zeta/src/rate_completion_modal.rs +++ b/crates/zeta/src/rate_completion_modal.rs @@ -267,13 +267,13 @@ impl RateCompletionModal { .unwrap_or(self.selected_index); cx.notify(); - if let Some(prev_completion) = self.active_completion.as_ref() { - if completion.id == prev_completion.completion.id { - if focus { - window.focus(&prev_completion.feedback_editor.focus_handle(cx)); - } - return; + if let Some(prev_completion) = self.active_completion.as_ref() + && completion.id == prev_completion.completion.id + { + if focus { + window.focus(&prev_completion.feedback_editor.focus_handle(cx)); } + return; } } @@ -607,7 +607,7 @@ impl Render for RateCompletionModal { .children(self.zeta.read(cx).shown_completions().cloned().enumerate().map( |(index, completion)| { let selected = - self.active_completion.as_ref().map_or(false, |selected| { + self.active_completion.as_ref().is_some_and(|selected| { selected.completion.id == completion.id }); let rated = diff --git a/crates/zeta/src/zeta.rs b/crates/zeta/src/zeta.rs index 1a6a8c2934..916699d29b 100644 --- a/crates/zeta/src/zeta.rs +++ b/crates/zeta/src/zeta.rs @@ -55,10 +55,10 @@ use workspace::Workspace; use workspace::notifications::{ErrorMessagePrompt, NotificationId}; use worktree::Worktree; -const CURSOR_MARKER: &'static str = "<|user_cursor_is_here|>"; -const START_OF_FILE_MARKER: &'static str = "<|start_of_file|>"; -const EDITABLE_REGION_START_MARKER: &'static str = "<|editable_region_start|>"; -const EDITABLE_REGION_END_MARKER: &'static str = "<|editable_region_end|>"; +const CURSOR_MARKER: &str = "<|user_cursor_is_here|>"; +const START_OF_FILE_MARKER: &str = "<|start_of_file|>"; +const EDITABLE_REGION_START_MARKER: &str = "<|editable_region_start|>"; +const EDITABLE_REGION_END_MARKER: &str = "<|editable_region_end|>"; const BUFFER_CHANGE_GROUPING_INTERVAL: Duration = Duration::from_secs(1); const ZED_PREDICT_DATA_COLLECTION_CHOICE: &str = "zed_predict_data_collection_choice"; @@ -106,7 +106,7 @@ impl Dismissable for ZedPredictUpsell { if KEY_VALUE_STORE .read_kvp(ZED_PREDICT_DATA_COLLECTION_CHOICE) .log_err() - .map_or(false, |s| s.is_some()) + .is_some_and(|s| s.is_some()) { return true; } @@ -114,7 +114,7 @@ impl Dismissable for ZedPredictUpsell { KEY_VALUE_STORE .read_kvp(Self::KEY) .log_err() - .map_or(false, |s| s.is_some()) + .is_some_and(|s| s.is_some()) } } @@ -166,7 +166,7 @@ fn interpolate( ) -> Option<Vec<(Range<Anchor>, String)>> { let mut edits = Vec::new(); - let mut model_edits = current_edits.into_iter().peekable(); + let mut model_edits = current_edits.iter().peekable(); for user_edit in new_snapshot.edits_since::<usize>(&old_snapshot.version) { while let Some((model_old_range, _)) = model_edits.peek() { let model_old_range = model_old_range.to_offset(old_snapshot); @@ -505,7 +505,7 @@ impl Zeta { input_events, input_excerpt, buffer_snapshotted_at, - &cx, + cx, ) .await; @@ -836,12 +836,11 @@ and then another .headers() .get(MINIMUM_REQUIRED_VERSION_HEADER_NAME) .and_then(|version| SemanticVersion::from_str(version.to_str().ok()?).ok()) + && app_version < minimum_required_version { - if app_version < minimum_required_version { - return Err(anyhow!(ZedUpdateRequiredError { - minimum_version: minimum_required_version - })); - } + return Err(anyhow!(ZedUpdateRequiredError { + minimum_version: minimum_required_version + })); } if response.status().is_success() { @@ -981,7 +980,7 @@ and then another old_text, new_text, editable_range.start, - &snapshot, + snapshot, )) } @@ -991,7 +990,7 @@ and then another offset: usize, snapshot: &BufferSnapshot, ) -> Vec<(Range<Anchor>, String)> { - text_diff(&old_text, &new_text) + text_diff(&old_text, new_text) .into_iter() .map(|(mut old_range, new_text)| { old_range.start += offset; @@ -1182,7 +1181,7 @@ pub fn gather_context( .filter_map(|(language_server_id, diagnostic_group)| { let language_server = local_lsp_store.running_language_server_for_id(language_server_id)?; - let diagnostic_group = diagnostic_group.resolve::<usize>(&snapshot); + let diagnostic_group = diagnostic_group.resolve::<usize>(snapshot); let language_server_name = language_server.name().to_string(); let serialized = serde_json::to_value(diagnostic_group).unwrap(); Some((language_server_name, serialized)) @@ -1313,10 +1312,10 @@ impl CurrentEditPrediction { return true; } - let Some(old_edits) = old_completion.completion.interpolate(&snapshot) else { + let Some(old_edits) = old_completion.completion.interpolate(snapshot) else { return true; }; - let Some(new_edits) = self.completion.interpolate(&snapshot) else { + let Some(new_edits) = self.completion.interpolate(snapshot) else { return false; }; @@ -1664,7 +1663,7 @@ impl edit_prediction::EditPredictionProvider for ZetaEditPredictionProvider { if let Some(old_completion) = this.current_completion.as_ref() { let snapshot = buffer.read(cx).snapshot(); - if new_completion.should_replace_completion(&old_completion, &snapshot) { + if new_completion.should_replace_completion(old_completion, &snapshot) { this.zeta.update(cx, |zeta, cx| { zeta.completion_shown(&new_completion.completion, cx); }); @@ -2124,7 +2123,7 @@ mod tests { let completion = completion_task.await.unwrap().unwrap(); completion .edits - .into_iter() + .iter() .map(|(old_range, new_text)| (old_range.to_point(&snapshot), new_text.clone())) .collect::<Vec<_>>() } diff --git a/crates/zeta_cli/src/main.rs b/crates/zeta_cli/src/main.rs index d78035bc9d..5b2d4cf615 100644 --- a/crates/zeta_cli/src/main.rs +++ b/crates/zeta_cli/src/main.rs @@ -131,7 +131,7 @@ async fn get_context( let (project, _lsp_open_handle, buffer) = if use_language_server { let (project, lsp_open_handle, buffer) = - open_buffer_with_language_server(&worktree_path, &cursor.path, &app_state, cx).await?; + open_buffer_with_language_server(&worktree_path, &cursor.path, app_state, cx).await?; (Some(project), Some(lsp_open_handle), buffer) } else { let abs_path = worktree_path.join(&cursor.path); @@ -190,9 +190,8 @@ async fn get_context( .await; // Disable data collection for these requests, as this is currently just used for evals - match gather_context_output.as_mut() { - Ok(gather_context_output) => gather_context_output.body.can_collect_data = false, - Err(_) => {} + if let Ok(gather_context_output) = gather_context_output.as_mut() { + gather_context_output.body.can_collect_data = false } gather_context_output @@ -260,7 +259,7 @@ pub fn wait_for_lang_server( .update(cx, |buffer, cx| { lsp_store.update(cx, |lsp_store, cx| { lsp_store - .language_servers_for_local_buffer(&buffer, cx) + .language_servers_for_local_buffer(buffer, cx) .next() .is_some() }) @@ -277,8 +276,8 @@ pub fn wait_for_lang_server( let subscriptions = [ cx.subscribe(&lsp_store, { let log_prefix = log_prefix.clone(); - move |_, event, _| match event { - project::LspStoreEvent::LanguageServerUpdate { + move |_, event, _| { + if let project::LspStoreEvent::LanguageServerUpdate { message: client::proto::update_language_server::Variant::WorkProgress( client::proto::LspWorkProgress { @@ -287,11 +286,13 @@ pub fn wait_for_lang_server( }, ), .. - } => println!("{}⟲ {message}", log_prefix), - _ => {} + } = event + { + println!("{}⟲ {message}", log_prefix) + } } }), - cx.subscribe(&project, { + cx.subscribe(project, { let buffer = buffer.clone(); move |project, event, cx| match event { project::Event::LanguageServerAdded(_, _, _) => { diff --git a/crates/zlog/src/filter.rs b/crates/zlog/src/filter.rs index 56350d34c3..36a77e37bd 100644 --- a/crates/zlog/src/filter.rs +++ b/crates/zlog/src/filter.rs @@ -4,7 +4,6 @@ use std::{ OnceLock, RwLock, atomic::{AtomicU8, Ordering}, }, - usize, }; use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, Scope, ScopeAlloc, env_config, private}; @@ -55,7 +54,7 @@ pub fn init_env_filter(filter: env_config::EnvFilter) { } pub fn is_possibly_enabled_level(level: log::Level) -> bool { - return level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Relaxed); + level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Relaxed) } pub fn is_scope_enabled(scope: &Scope, module_path: Option<&str>, level: log::Level) -> bool { @@ -70,7 +69,7 @@ pub fn is_scope_enabled(scope: &Scope, module_path: Option<&str>, level: log::Le let is_enabled_by_default = level <= unsafe { LEVEL_ENABLED_MAX_STATIC }; let global_scope_map = SCOPE_MAP.read().unwrap_or_else(|err| { SCOPE_MAP.clear_poison(); - return err.into_inner(); + err.into_inner() }); let Some(map) = global_scope_map.as_ref() else { @@ -82,12 +81,12 @@ pub fn is_scope_enabled(scope: &Scope, module_path: Option<&str>, level: log::Le // if no scopes are enabled, return false because it's not <= LEVEL_ENABLED_MAX_STATIC return is_enabled_by_default; } - let enabled_status = map.is_enabled(&scope, module_path, level); - return match enabled_status { + let enabled_status = map.is_enabled(scope, module_path, level); + match enabled_status { EnabledStatus::NotConfigured => is_enabled_by_default, EnabledStatus::Enabled => true, EnabledStatus::Disabled => false, - }; + } } pub fn refresh_from_settings(settings: &HashMap<String, String>) { @@ -132,7 +131,7 @@ fn level_filter_from_str(level_str: &str) -> Option<log::LevelFilter> { return None; } }; - return Some(level); + Some(level) } fn scope_alloc_from_scope_str(scope_str: &str) -> Option<ScopeAlloc> { @@ -143,7 +142,7 @@ fn scope_alloc_from_scope_str(scope_str: &str) -> Option<ScopeAlloc> { let Some(scope) = scope_iter.next() else { break; }; - if scope == "" { + if scope.is_empty() { continue; } scope_buf[index] = scope; @@ -152,14 +151,14 @@ fn scope_alloc_from_scope_str(scope_str: &str) -> Option<ScopeAlloc> { if index == 0 { return None; } - if let Some(_) = scope_iter.next() { + if scope_iter.next().is_some() { crate::warn!( "Invalid scope key, too many nested scopes: '{scope_str}'. Max depth is {SCOPE_DEPTH_MAX}", ); return None; } let scope = scope_buf.map(|s| s.to_string()); - return Some(scope); + Some(scope) } #[derive(Debug, PartialEq, Eq)] @@ -204,12 +203,10 @@ impl ScopeMap { .map(|(scope_str, level_filter)| (scope_str.as_str(), *level_filter)) }); - let new_filters = items_input_map - .into_iter() - .filter_map(|(scope_str, level_str)| { - let level_filter = level_filter_from_str(level_str)?; - Some((scope_str.as_str(), level_filter)) - }); + let new_filters = items_input_map.iter().filter_map(|(scope_str, level_str)| { + let level_filter = level_filter_from_str(level_str)?; + Some((scope_str.as_str(), level_filter)) + }); let all_filters = default_filters .iter() @@ -280,7 +277,7 @@ impl ScopeMap { cursor += 1; } let sub_items_end = cursor; - if scope_name == "" { + if scope_name.is_empty() { assert_eq!(sub_items_start + 1, sub_items_end); assert_ne!(depth, 0); assert_ne!(parent_index, usize::MAX); @@ -288,7 +285,7 @@ impl ScopeMap { this.entries[parent_index].enabled = Some(items[sub_items_start].1); continue; } - let is_valid_scope = scope_name != ""; + let is_valid_scope = !scope_name.is_empty(); let is_last = depth + 1 == SCOPE_DEPTH_MAX || !is_valid_scope; let mut enabled = None; if is_last { @@ -321,7 +318,7 @@ impl ScopeMap { } } - return this; + this } pub fn is_empty(&self) -> bool { @@ -358,7 +355,7 @@ impl ScopeMap { } break 'search; } - return enabled; + enabled } let mut enabled = search(self, scope); @@ -394,7 +391,7 @@ impl ScopeMap { } return EnabledStatus::Disabled; } - return EnabledStatus::NotConfigured; + EnabledStatus::NotConfigured } } @@ -456,7 +453,7 @@ mod tests { let Some(scope) = scope_iter.next() else { break; }; - if scope == "" { + if scope.is_empty() { continue; } scope_buf[index] = scope; @@ -464,7 +461,7 @@ mod tests { } assert_ne!(index, 0); assert!(scope_iter.next().is_none()); - return scope_buf; + scope_buf } #[test] diff --git a/crates/zlog/src/sink.rs b/crates/zlog/src/sink.rs index 17aa08026e..3ac85d4bbf 100644 --- a/crates/zlog/src/sink.rs +++ b/crates/zlog/src/sink.rs @@ -194,10 +194,10 @@ pub fn flush() { ENABLED_SINKS_FILE.clear_poison(); handle.into_inner() }); - if let Some(file) = file.as_mut() { - if let Err(err) = file.flush() { - eprintln!("Failed to flush log file: {}", err); - } + if let Some(file) = file.as_mut() + && let Err(err) = file.flush() + { + eprintln!("Failed to flush log file: {}", err); } } diff --git a/crates/zlog/src/zlog.rs b/crates/zlog/src/zlog.rs index 5b40278f3f..d0e8958df5 100644 --- a/crates/zlog/src/zlog.rs +++ b/crates/zlog/src/zlog.rs @@ -10,12 +10,9 @@ pub use sink::{flush, init_output_file, init_output_stderr, init_output_stdout}; pub const SCOPE_DEPTH_MAX: usize = 4; pub fn init() { - match try_init() { - Err(err) => { - log::error!("{err}"); - eprintln!("{err}"); - } - Ok(()) => {} + if let Err(err) = try_init() { + log::error!("{err}"); + eprintln!("{err}"); } } @@ -28,10 +25,8 @@ pub fn try_init() -> anyhow::Result<()> { } pub fn init_test() { - if get_env_config().is_some() { - if try_init().is_ok() { - init_output_stdout(); - } + if get_env_config().is_some() && try_init().is_ok() { + init_output_stdout(); } } @@ -242,7 +237,7 @@ pub mod private { let Some((crate_name, _)) = module_path.split_at_checked(index) else { return module_path; }; - return crate_name; + crate_name } pub const fn scope_new(scopes: &[&'static str]) -> Scope { @@ -264,13 +259,13 @@ pub mod private { } pub fn scope_to_alloc(scope: &Scope) -> ScopeAlloc { - return scope.map(|s| s.to_string()); + scope.map(|s| s.to_string()) } } pub type Scope = [&'static str; SCOPE_DEPTH_MAX]; pub type ScopeAlloc = [String; SCOPE_DEPTH_MAX]; -const SCOPE_STRING_SEP_STR: &'static str = "."; +const SCOPE_STRING_SEP_STR: &str = "."; const SCOPE_STRING_SEP_CHAR: char = '.'; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -321,18 +316,18 @@ impl Drop for Timer { impl Timer { #[must_use = "Timer will stop when dropped, the result of this function should be saved in a variable prefixed with `_` if it should stop when dropped"] pub fn new(logger: Logger, name: &'static str) -> Self { - return Self { + Self { logger, name, start_time: std::time::Instant::now(), warn_if_longer_than: None, done: false, - }; + } } pub fn warn_if_gt(mut self, warn_limit: std::time::Duration) -> Self { self.warn_if_longer_than = Some(warn_limit); - return self; + self } pub fn end(mut self) { @@ -344,18 +339,18 @@ impl Timer { return; } let elapsed = self.start_time.elapsed(); - if let Some(warn_limit) = self.warn_if_longer_than { - if elapsed > warn_limit { - crate::warn!( - self.logger => - "Timer '{}' took {:?}. Which was longer than the expected limit of {:?}", - self.name, - elapsed, - warn_limit - ); - self.done = true; - return; - } + if let Some(warn_limit) = self.warn_if_longer_than + && elapsed > warn_limit + { + crate::warn!( + self.logger => + "Timer '{}' took {:?}. Which was longer than the expected limit of {:?}", + self.name, + elapsed, + warn_limit + ); + self.done = true; + return; } crate::trace!( self.logger => diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 9d56130256..39d172ea5f 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -539,12 +539,6 @@ List of `string` values - Setting: `selection_highlight` - Default: `true` -## LSP Highlight Debounce - -- Description: The debounce delay before querying highlights from the language server based on the current cursor location. -- Setting: `lsp_highlight_debounce` -- Default: `75` - ## Cursor Blink - Description: Whether or not the cursor blinks. @@ -1339,6 +1333,18 @@ While other options may be changed at a runtime and should be placed under `sett - Setting: `lsp_highlight_debounce` - Default: `75` +## Global LSP Settings + +- Description: Common language server settings. +- Setting: `global_lsp_settings` +- Default: + +```json +"global_lsp_settings": { + "button": true +} +``` + **Options** `integer` values representing milliseconds diff --git a/docs/src/key-bindings.md b/docs/src/key-bindings.md index 9fc94840b7..838dceaa86 100644 --- a/docs/src/key-bindings.md +++ b/docs/src/key-bindings.md @@ -225,12 +225,14 @@ A common request is to be able to map from a single keystroke to a sequence. You [ { "bindings": { + // Move down four times "alt-down": ["workspace::SendKeystrokes", "down down down down"], + // Expand the selection (editor::SelectLargerSyntaxNode); + // copy to the clipboard; and then undo the selection expansion. "cmd-alt-c": [ "workspace::SendKeystrokes", - "cmd-shift-p copy relative path enter" - ], - "cmd-alt-r": ["workspace::SendKeystrokes", "cmd-p README enter"] + "ctrl-shift-right ctrl-shift-right ctrl-shift-right cmd-c ctrl-shift-left ctrl-shift-left ctrl-shift-left" + ] } }, { diff --git a/docs/src/visual-customization.md b/docs/src/visual-customization.md index 6e598f4436..3ad1e381d9 100644 --- a/docs/src/visual-customization.md +++ b/docs/src/visual-customization.md @@ -321,6 +321,12 @@ TBD: Centered layout related settings // Defaults to true. "cursor_position_button": true, }, + "global_lsp_settings": { + // Show/hide the LSP button in the status bar. + // Activity from the LSP is still shown. + // Button is not shown if "enable_language_server" if false. + "button": true + }, ``` ### Multibuffer diff --git a/extensions/glsl/src/glsl.rs b/extensions/glsl/src/glsl.rs index a42403ebef..695fd7a053 100644 --- a/extensions/glsl/src/glsl.rs +++ b/extensions/glsl/src/glsl.rs @@ -16,10 +16,10 @@ impl GlslExtension { return Ok(path); } - if let Some(path) = &self.cached_binary_path { - if fs::metadata(path).map_or(false, |stat| stat.is_file()) { - return Ok(path.clone()); - } + if let Some(path) = &self.cached_binary_path + && fs::metadata(path).is_ok_and(|stat| stat.is_file()) + { + return Ok(path.clone()); } zed::set_language_server_installation_status( @@ -60,7 +60,7 @@ impl GlslExtension { .map_err(|err| format!("failed to create directory '{version_dir}': {err}"))?; let binary_path = format!("{version_dir}/bin/glsl_analyzer"); - if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { zed::set_language_server_installation_status( language_server_id, &zed::LanguageServerInstallationStatus::Downloading, diff --git a/extensions/html/src/html.rs b/extensions/html/src/html.rs index 44ec4fe4b9..07d4642ff4 100644 --- a/extensions/html/src/html.rs +++ b/extensions/html/src/html.rs @@ -13,7 +13,7 @@ struct HtmlExtension { impl HtmlExtension { fn server_exists(&self) -> bool { - fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file()) + fs::metadata(SERVER_PATH).is_ok_and(|stat| stat.is_file()) } fn server_script_path(&mut self, language_server_id: &LanguageServerId) -> Result<String> { diff --git a/extensions/ruff/src/ruff.rs b/extensions/ruff/src/ruff.rs index da9b6c0bf1..b918c52686 100644 --- a/extensions/ruff/src/ruff.rs +++ b/extensions/ruff/src/ruff.rs @@ -38,13 +38,13 @@ impl RuffExtension { }); } - if let Some(path) = &self.cached_binary_path { - if fs::metadata(path).map_or(false, |stat| stat.is_file()) { - return Ok(RuffBinary { - path: path.clone(), - args: binary_args, - }); - } + if let Some(path) = &self.cached_binary_path + && fs::metadata(path).is_ok_and(|stat| stat.is_file()) + { + return Ok(RuffBinary { + path: path.clone(), + args: binary_args, + }); } zed::set_language_server_installation_status( @@ -94,7 +94,7 @@ impl RuffExtension { _ => format!("{version_dir}/{asset_stem}/ruff"), }; - if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { zed::set_language_server_installation_status( language_server_id, &zed::LanguageServerInstallationStatus::Downloading, diff --git a/extensions/snippets/src/snippets.rs b/extensions/snippets/src/snippets.rs index 46ba746930..b2d68b6e1a 100644 --- a/extensions/snippets/src/snippets.rs +++ b/extensions/snippets/src/snippets.rs @@ -17,10 +17,10 @@ impl SnippetExtension { return Ok(path); } - if let Some(path) = &self.cached_binary_path { - if fs::metadata(path).map_or(false, |stat| stat.is_file()) { - return Ok(path.clone()); - } + if let Some(path) = &self.cached_binary_path + && fs::metadata(path).is_ok_and(|stat| stat.is_file()) + { + return Ok(path.clone()); } zed::set_language_server_installation_status( @@ -59,7 +59,7 @@ impl SnippetExtension { let version_dir = format!("simple-completion-language-server-{}", release.version); let binary_path = format!("{version_dir}/simple-completion-language-server"); - if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { zed::set_language_server_installation_status( language_server_id, &zed::LanguageServerInstallationStatus::Downloading, diff --git a/extensions/test-extension/src/test_extension.rs b/extensions/test-extension/src/test_extension.rs index 5b6a3f920a..ee0b1b36a1 100644 --- a/extensions/test-extension/src/test_extension.rs +++ b/extensions/test-extension/src/test_extension.rs @@ -18,10 +18,10 @@ impl TestExtension { println!("{}", String::from_utf8_lossy(&echo_output.stdout)); - if let Some(path) = &self.cached_binary_path { - if fs::metadata(path).map_or(false, |stat| stat.is_file()) { - return Ok(path.clone()); - } + if let Some(path) = &self.cached_binary_path + && fs::metadata(path).is_ok_and(|stat| stat.is_file()) + { + return Ok(path.clone()); } zed::set_language_server_installation_status( @@ -61,7 +61,7 @@ impl TestExtension { let version_dir = format!("gleam-{}", release.version); let binary_path = format!("{version_dir}/gleam"); - if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { zed::set_language_server_installation_status( language_server_id, &zed::LanguageServerInstallationStatus::Downloading, diff --git a/extensions/toml/src/toml.rs b/extensions/toml/src/toml.rs index 20f27b6d97..c9b96aecac 100644 --- a/extensions/toml/src/toml.rs +++ b/extensions/toml/src/toml.rs @@ -39,13 +39,13 @@ impl TomlExtension { }); } - if let Some(path) = &self.cached_binary_path { - if fs::metadata(path).map_or(false, |stat| stat.is_file()) { - return Ok(TaploBinary { - path: path.clone(), - args: binary_args, - }); - } + if let Some(path) = &self.cached_binary_path + && fs::metadata(path).is_ok_and(|stat| stat.is_file()) + { + return Ok(TaploBinary { + path: path.clone(), + args: binary_args, + }); } zed::set_language_server_installation_status( @@ -93,7 +93,7 @@ impl TomlExtension { } ); - if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { zed::set_language_server_installation_status( language_server_id, &zed::LanguageServerInstallationStatus::Downloading, diff --git a/tooling/xtask/src/tasks/package_conformity.rs b/tooling/xtask/src/tasks/package_conformity.rs index c82b9cdf84..c8bed4bb35 100644 --- a/tooling/xtask/src/tasks/package_conformity.rs +++ b/tooling/xtask/src/tasks/package_conformity.rs @@ -21,13 +21,11 @@ pub fn run_package_conformity(_args: PackageConformityArgs) -> Result<()> { .manifest_path .parent() .and_then(|parent| parent.parent()) - .map_or(false, |grandparent_dir| { - grandparent_dir.ends_with("extensions") - }); + .is_some_and(|grandparent_dir| grandparent_dir.ends_with("extensions")); let cargo_toml = read_cargo_toml(&package.manifest_path)?; - let is_using_workspace_lints = cargo_toml.lints.map_or(false, |lints| lints.workspace); + let is_using_workspace_lints = cargo_toml.lints.is_some_and(|lints| lints.workspace); if !is_using_workspace_lints { eprintln!( "{package:?} is not using workspace lints",